diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 5ad87e0df46..491274ccf04 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@  - 10.1.5-beta04 + 10.1.5-beta05 diff --git a/src/BootstrapBlazor/Components/Upload/AvatarUpload.razor.cs b/src/BootstrapBlazor/Components/Upload/AvatarUpload.razor.cs index bcb5313ba89..ce437af34e2 100644 --- a/src/BootstrapBlazor/Components/Upload/AvatarUpload.razor.cs +++ b/src/BootstrapBlazor/Components/Upload/AvatarUpload.razor.cs @@ -187,14 +187,10 @@ public override async Task ToggleMessage(IReadOnlyCollection r ValidateModule ??= await LoadValidateModule(); - var invalidItems = IsInValidOnAddItem - ? [new { Id = AddId, _results.First().ErrorMessage }] - : _results.Select(i => new { Id = i.MemberNames.FirstOrDefault(), i.ErrorMessage }).ToList(); - var items = IsInValidOnAddItem ? [AddId] : Files.Select(i => i.ValidateId).ToList(); - + var invalidItems = _results.GetInvalidItems(IsInValidOnAddItem, AddId); var addId = IsInValidOnAddItem ? null : AddId; await ValidateModule.InvokeVoidAsync("executeUpload", items, invalidItems, addId); } diff --git a/src/BootstrapBlazor/Components/Upload/CardUpload.razor.cs b/src/BootstrapBlazor/Components/Upload/CardUpload.razor.cs index 5a3458799cc..7f746015eb1 100644 --- a/src/BootstrapBlazor/Components/Upload/CardUpload.razor.cs +++ b/src/BootstrapBlazor/Components/Upload/CardUpload.razor.cs @@ -176,14 +176,10 @@ public override async Task ToggleMessage(IReadOnlyCollection r ValidateModule ??= await LoadValidateModule(); - var invalidItems = IsInValidOnAddItem - ? [new { Id = AddId, _results.First().ErrorMessage }] - : _results.Select(i => new { Id = i.MemberNames.FirstOrDefault(), i.ErrorMessage }).ToList(); - var items = IsInValidOnAddItem ? [AddId] : Files.Select(i => i.ValidateId).ToList(); - + var invalidItems = _results.GetInvalidItems(IsInValidOnAddItem, AddId); var addId = IsInValidOnAddItem ? null : AddId; await ValidateModule.InvokeVoidAsync("executeUpload", items, invalidItems, addId); } diff --git a/src/BootstrapBlazor/Components/Upload/FileListUploadBase.cs b/src/BootstrapBlazor/Components/Upload/FileListUploadBase.cs index 8a29dda16c0..c42e230eeb4 100644 --- a/src/BootstrapBlazor/Components/Upload/FileListUploadBase.cs +++ b/src/BootstrapBlazor/Components/Upload/FileListUploadBase.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the Apache 2.0 License // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone @@ -6,7 +6,7 @@ namespace BootstrapBlazor.Components; /// -/// PreviewListUploadBase 基类 +/// FileListUploadBase 基类 /// /// public class FileListUploadBase : UploadBase diff --git a/src/BootstrapBlazor/Components/Upload/UploadBase.cs b/src/BootstrapBlazor/Components/Upload/UploadBase.cs index ffc59032989..acbf0d63373 100644 --- a/src/BootstrapBlazor/Components/Upload/UploadBase.cs +++ b/src/BootstrapBlazor/Components/Upload/UploadBase.cs @@ -190,6 +190,11 @@ protected async Task OnFileChange(InputFileChangeEventArgs args) await OnAllFileUploaded(items); } + UpdateValue(items); + } + + private void UpdateValue(List items) + { if (ValueType.IsAssignableTo(typeof(IEnumerable))) { CurrentValue = (TValue)(object)items; @@ -248,6 +253,8 @@ protected virtual async Task OnFileDelete(UploadFile item) UploadFiles.Remove(item); DefaultFileList?.Remove(item); _filesCache = null; + + UpdateValue(Files); } StateHasChanged(); return ret; diff --git a/src/BootstrapBlazor/Components/Upload/UploadValidateItem.cs b/src/BootstrapBlazor/Components/Upload/UploadValidateItem.cs new file mode 100644 index 00000000000..9088c2555af --- /dev/null +++ b/src/BootstrapBlazor/Components/Upload/UploadValidateItem.cs @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the Apache 2.0 License +// See the LICENSE file in the project root for more information. +// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone + +namespace BootstrapBlazor.Components; + +readonly record struct UploadValidateItem(string? Id, string? ErrorMessage); diff --git a/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs b/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs index e277287199f..4cd3093e639 100644 --- a/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs +++ b/src/BootstrapBlazor/Components/ValidateForm/ValidateForm.razor.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Components.Forms; using Microsoft.Extensions.Localization; +using System.Collections; using System.Collections.Concurrent; using System.Linq.Expressions; using System.Reflection; @@ -508,7 +509,14 @@ private async Task ValidateAsync(IValidateComponent validator, ValidationContext else { // 未选择文件 - propertyValue = null; + if (propertyValue is string) + { + + } + else if (propertyValue is IEnumerable) + { + propertyValue = null; + } ValidateDataAnnotations(propertyValue, context, messages, pi); } diff --git a/src/BootstrapBlazor/Extensions/ValidateContextExtensions.cs b/src/BootstrapBlazor/Extensions/ValidateContextExtensions.cs index 2c6e01eea92..83aab3ad6cb 100644 --- a/src/BootstrapBlazor/Extensions/ValidateContextExtensions.cs +++ b/src/BootstrapBlazor/Extensions/ValidateContextExtensions.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the Apache 2.0 License // See the LICENSE file in the project root for more information. // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone @@ -42,4 +42,8 @@ public static ValidationResult GetValidationResult(this ValidationContext contex var memberNames = string.IsNullOrEmpty(context.MemberName) ? null : new string[] { context.MemberName }; return new ValidationResult(errorMessage, memberNames); } + + internal static List GetInvalidItems(this IReadOnlyCollection source, bool isInValidOnAddItem, string? newId) => isInValidOnAddItem + ? [new UploadValidateItem() { Id = newId, ErrorMessage = source.First().ErrorMessage }] + : source.Select(i => new UploadValidateItem() { Id = i.MemberNames.FirstOrDefault(), ErrorMessage = i.ErrorMessage }).ToList(); } diff --git a/test/UnitTest/Components/UploadAvatarTest.cs b/test/UnitTest/Components/UploadAvatarTest.cs index 79b0e09ec43..8e888b493d5 100644 --- a/test/UnitTest/Components/UploadAvatarTest.cs +++ b/test/UnitTest/Components/UploadAvatarTest.cs @@ -286,6 +286,26 @@ await cut.InvokeAsync(() => input.Instance.OnChange.InvokeAsync(new InputFileCha await cut.InvokeAsync(() => items[1].Click()); } + [Fact] + public void UploadValidateItem_Ok() + { + var type = Type.GetType("BootstrapBlazor.Components.UploadValidateItem, BootstrapBlazor"); + Assert.NotNull(type); + + var instance = Activator.CreateInstance(type, ["addId", "mock_ErrorMessage"]); + var propertyInfo = type.GetProperty("Id"); + Assert.NotNull(propertyInfo); + + var v = propertyInfo.GetValue(instance, null); + Assert.Equal("addId", v); + + propertyInfo = type.GetProperty("ErrorMessage"); + Assert.NotNull(propertyInfo); + + v = propertyInfo.GetValue(instance, null); + Assert.Equal("mock_ErrorMessage", v); + } + private class Person { [Required] diff --git a/test/UnitTest/Components/UploadCardTest.cs b/test/UnitTest/Components/UploadCardTest.cs index 8b7cb5c303d..fd2bf363a11 100644 --- a/test/UnitTest/Components/UploadCardTest.cs +++ b/test/UnitTest/Components/UploadCardTest.cs @@ -4,6 +4,7 @@ // Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone using Microsoft.AspNetCore.Components.Forms; +using System.ComponentModel.DataAnnotations; namespace UnitTest.Components; @@ -150,20 +151,77 @@ public void ShowFileSize_Ok() cut.DoesNotContain("upload-item-file-size"); } + private class Dummy + { + [Required] + public List? Files { get; set; } + } + [Fact] - public void CardUpload_ValidateForm_Ok() + public async Task CardUpload_ValidateForm_Ok() { - var foo = new Foo(); + var invalid = false; + var foo = new Dummy(); var cut = Context.Render(pb => { pb.Add(a => a.Model, foo); - pb.AddChildContent>(pb => + pb.AddChildContent>>(pb => + { + pb.Add(a => a.Accept, "Image"); + pb.Add(a => a.Value, foo.Files); + pb.Add(a => a.ValueChanged, EventCallback.Factory.Create?>(this, v => foo.Files = v)); + pb.Add(a => a.ValueExpression, Utility.GenerateValueExpression(foo, "Files", typeof(List))); + pb.Add(a => a.AllowExtensions, [".jpg"]); + pb.Add(a => a.ShowDeleteButton, true); + }); + pb.Add(a => a.OnValidSubmit, context => + { + invalid = false; + return Task.CompletedTask; + }); + pb.Add(a => a.OnInvalidSubmit, context => { - pb.Add(a => a.Value, foo.Name); - pb.Add(a => a.ValueExpression, foo.GenerateValueExpression()); + invalid = true; + return Task.CompletedTask; }); }); - cut.Contains("form-label"); + + // 提交表单 + var form = cut.Find("form"); + await cut.InvokeAsync(() => form.Submit()); + Assert.True(invalid); + + var input = cut.FindComponent(); + await cut.InvokeAsync(async () => + { + await input.Instance.OnChange.InvokeAsync(new InputFileChangeEventArgs(new List() + { + new() + })); + form.Submit(); + }); + Assert.False(invalid); + + // 设置 Disabled 取消校验 + var upload = cut.FindComponent>>(); + upload.Render(pb => + { + pb.Add(a => a.IsDisabled, true); + }); + + Assert.DoesNotContain("is-invalid", upload.Markup); + + upload.Render(pb => + { + pb.Add(a => a.IsDisabled, false); + }); + + var items = cut.FindAll(".btn-outline-danger"); + Assert.Single(items); + await cut.InvokeAsync(() => items[0].Click()); + + form = cut.Find("form"); + await cut.InvokeAsync(() => form.Submit()); } [Fact]