diff --git a/src/BootstrapBlazor/Attributes/AutoGenerateColumnAttribute.cs b/src/BootstrapBlazor/Attributes/AutoGenerateColumnAttribute.cs index b2ccff5ad0f..4f4d105913a 100644 --- a/src/BootstrapBlazor/Attributes/AutoGenerateColumnAttribute.cs +++ b/src/BootstrapBlazor/Attributes/AutoGenerateColumnAttribute.cs @@ -327,6 +327,11 @@ public class AutoGenerateColumnAttribute : AutoGenerateBaseAttribute, ITableColu /// public object? LookupServiceData { get; set; } + /// + /// + /// + ILookupService? IEditorItem.LookupService { get; set; } + /// /// 获得/设置 单元格回调方法 /// diff --git a/src/BootstrapBlazor/BootstrapBlazor.csproj b/src/BootstrapBlazor/BootstrapBlazor.csproj index 066a96ef890..99d0077a1cd 100644 --- a/src/BootstrapBlazor/BootstrapBlazor.csproj +++ b/src/BootstrapBlazor/BootstrapBlazor.csproj @@ -1,7 +1,7 @@ - 9.1.3-beta01 + 9.1.3-beta02 diff --git a/src/BootstrapBlazor/Components/EditorForm/EditorForm.razor b/src/BootstrapBlazor/Components/EditorForm/EditorForm.razor index 2b1d0a714bf..a3a6de869f1 100644 --- a/src/BootstrapBlazor/Components/EditorForm/EditorForm.razor +++ b/src/BootstrapBlazor/Components/EditorForm/EditorForm.razor @@ -7,10 +7,7 @@ @FieldItems?.Invoke(Model) - - @{ - ResetItems(); - } + @if (ShowUnsetGroupItemsOnTop) { if (UnsetGroupItems.Any()) diff --git a/src/BootstrapBlazor/Components/EditorForm/EditorForm.razor.cs b/src/BootstrapBlazor/Components/EditorForm/EditorForm.razor.cs index 0d8c6318b68..0b59bac6ca0 100644 --- a/src/BootstrapBlazor/Components/EditorForm/EditorForm.razor.cs +++ b/src/BootstrapBlazor/Components/EditorForm/EditorForm.razor.cs @@ -184,19 +184,25 @@ public partial class EditorForm : IShowLabel /// private readonly List _editorItems = []; - /// - /// 获得/设置 渲染的编辑项集合 - /// - [NotNull] - private List? _formItems = null; + private IEnumerable UnsetGroupItems => RenderItems.Where(i => string.IsNullOrEmpty(i.GroupName) && i.IsVisible(ItemChangedType, IsSearch.Value)); - private IEnumerable UnsetGroupItems => _formItems.Where(i => string.IsNullOrEmpty(i.GroupName) && i.IsVisible(ItemChangedType, IsSearch.Value)); - - private IEnumerable>> GroupItems => _formItems + private IEnumerable>> GroupItems => RenderItems .Where(i => !string.IsNullOrEmpty(i.GroupName) && i.IsVisible(ItemChangedType, IsSearch.Value)) .GroupBy(i => i.GroupOrder).OrderBy(i => i.Key) .Select(i => new KeyValuePair>(i.First().GroupName!, i.OrderBy(x => x.Order))); + private bool _inited; + private List? _itemsCache; + + private List RenderItems + { + get + { + _itemsCache ??= GetRenderItems(); + return _itemsCache; + } + } + /// /// OnInitialized 方法 /// @@ -217,7 +223,6 @@ protected override void OnInitialized() // 统一设置所有 IEditorItem 的 PlaceHolder PlaceHolderText ??= Localizer[nameof(PlaceHolderText)]; - IsSearch ??= false; } @@ -230,66 +235,75 @@ protected override void OnParametersSet() // 为空时使用级联参数 ValidateForm 的 ShowLabel ShowLabel ??= ValidateForm?.ShowLabel; - _formItems = null; + _itemsCache = null; } - private bool _inited; - - private Task OnRenderAsync(bool firstRender) + /// + /// + /// + /// + protected override async Task OnAfterRenderAsync(bool firstRender) { + await base.OnAfterRenderAsync(firstRender); + if (firstRender) { + foreach (var item in RenderItems) + { + if (item.Lookup == null && !string.IsNullOrEmpty(item.LookupServiceKey)) + { + var lookupServcie = item.LookupService ?? LookupService; + item.Lookup = await lookupServcie.GetItemsAsync(item.LookupServiceKey, item.LookupServiceData); + } + } _inited = true; StateHasChanged(); } - return Task.CompletedTask; } - private void ResetItems() + private List GetRenderItems() { - if (_formItems == null) + var items = new List(); + if (Items != null) { - _formItems = []; - if (Items != null) - { - _formItems.AddRange(Items.Where(i => !i.GetIgnore() && !string.IsNullOrEmpty(i.GetFieldName()))); - } - else + items.AddRange(Items.Where(i => !i.GetIgnore() && !string.IsNullOrEmpty(i.GetFieldName()))); + } + else + { + // 如果 EditorItems 有值表示 用户自定义列 + if (AutoGenerateAllItem) { - // 如果 EditorItems 有值表示 用户自定义列 - if (AutoGenerateAllItem) - { - // 获取绑定模型所有属性 - var items = Utility.GetTableColumns(defaultOrderCallback: ColumnOrderCallback).ToList(); + // 获取绑定模型所有属性 + var columns = Utility.GetTableColumns(defaultOrderCallback: ColumnOrderCallback).ToList(); - // 通过设定的 FieldItems 模板获取项进行渲染 - foreach (var el in _editorItems) + // 通过设定的 FieldItems 模板获取项进行渲染 + foreach (var el in _editorItems) + { + var item = columns.FirstOrDefault(i => i.GetFieldName() == el.GetFieldName()); + if (item != null) { - var item = items.FirstOrDefault(i => i.GetFieldName() == el.GetFieldName()); - if (item != null) + // 过滤掉不编辑与不可见的列 + if (el.GetIgnore() || !el.IsVisible(ItemChangedType, IsSearch.Value) || string.IsNullOrEmpty(el.GetFieldName())) { - // 过滤掉不编辑与不可见的列 - if (el.GetIgnore() || !el.IsVisible(ItemChangedType, IsSearch.Value)) - { - items.Remove(item); - } - else - { - // 设置只读属性与列模板 - item.CopyValue(el); - } + columns.Remove(item); + } + else + { + // 设置只读属性与列模板 + item.CopyValue(el); } } - _formItems.AddRange(items.Where(i => !string.IsNullOrEmpty(i.GetFieldName()))); - } - else - { - _formItems.AddRange(_editorItems.Where(i => !i.GetIgnore() - && !string.IsNullOrEmpty(i.GetFieldName()) - && i.IsVisible(ItemChangedType, IsSearch.Value))); } + items.AddRange(columns); + } + else + { + items.AddRange(_editorItems.Where(i => !i.GetIgnore() + && !string.IsNullOrEmpty(i.GetFieldName()) + && i.IsVisible(ItemChangedType, IsSearch.Value))); } } + return items; } private RenderFragment AutoGenerateTemplate(IEditorItem item) => builder => @@ -301,7 +315,7 @@ private RenderFragment AutoGenerateTemplate(IEditorItem item) => builder => else { item.PlaceHolder ??= PlaceHolderText; - builder.CreateComponentByFieldType(this, item, Model, ItemChangedType, IsSearch.Value, LookupService); + builder.CreateComponentByFieldType(this, item, Model, ItemChangedType, IsSearch.Value); } }; diff --git a/src/BootstrapBlazor/Components/EditorForm/EditorItem.cs b/src/BootstrapBlazor/Components/EditorForm/EditorItem.cs index 4074beda37a..125799312cf 100644 --- a/src/BootstrapBlazor/Components/EditorForm/EditorItem.cs +++ b/src/BootstrapBlazor/Components/EditorForm/EditorItem.cs @@ -194,6 +194,12 @@ public class EditorItem : ComponentBase, IEditorItem [Parameter] public object? LookupServiceData { get; set; } + /// + /// + /// + [Parameter] + public ILookupService? LookupService { get; set; } + /// /// 获得/设置 自定义验证集合 /// diff --git a/src/BootstrapBlazor/Components/EditorForm/IEditorItem.cs b/src/BootstrapBlazor/Components/EditorForm/IEditorItem.cs index 714c782086d..b309898d9c0 100644 --- a/src/BootstrapBlazor/Components/EditorForm/IEditorItem.cs +++ b/src/BootstrapBlazor/Components/EditorForm/IEditorItem.cs @@ -111,6 +111,11 @@ public interface IEditorItem /// object? LookupServiceData { get; set; } + /// + /// 获得/设置 服务实例 + /// + ILookupService? LookupService { get; set; } + /// /// 获得/设置 自定义验证集合 /// diff --git a/src/BootstrapBlazor/Components/Filters/LookupFilter.razor b/src/BootstrapBlazor/Components/Filters/LookupFilter.razor index 671396d16c0..ade21e8e2ee 100644 --- a/src/BootstrapBlazor/Components/Filters/LookupFilter.razor +++ b/src/BootstrapBlazor/Components/Filters/LookupFilter.razor @@ -1,11 +1,14 @@ @namespace BootstrapBlazor.Components @inherits FilterBase -@if (IsHeaderRow) +@if(Items != null) { - -} -else -{ - + if (IsHeaderRow) + { + + } + else + { + + } } diff --git a/src/BootstrapBlazor/Components/Filters/LookupFilter.razor.cs b/src/BootstrapBlazor/Components/Filters/LookupFilter.razor.cs index 5b722e51e52..5b9e33c4c8d 100644 --- a/src/BootstrapBlazor/Components/Filters/LookupFilter.razor.cs +++ b/src/BootstrapBlazor/Components/Filters/LookupFilter.razor.cs @@ -17,13 +17,31 @@ public partial class LookupFilter /// /// 获得/设置 相关枚举类型 /// -#if NET6_0_OR_GREATER - [EditorRequired] -#endif [Parameter] [NotNull] public IEnumerable? Lookup { get; set; } + /// + /// 获得/设置 服务实例 + /// + [Parameter] + [NotNull] + public ILookupService? LookupService { get; set; } + + /// + /// 获得/设置 服务获取 Lookup 数据集合键值 常用于外键自动转换为名称操作,可以通过 传递自定义数据 + /// + [Parameter] + [NotNull] + public string? LookupServiceKey { get; set; } + + /// + /// 获得/设置 服务获取 Lookup 数据集合键值自定义数据,通过 指定键值 + /// + [Parameter] + [NotNull] + public object? LookupServiceData { get; set; } + /// /// 获得/设置 字典数据源字符串比较规则 默认 StringComparison.OrdinalIgnoreCase 大小写不敏感 /// @@ -33,9 +51,7 @@ public partial class LookupFilter /// /// 获得/设置 相关枚举类型 /// -#if NET6_0_OR_GREATER [EditorRequired] -#endif [Parameter] [NotNull] public Type? Type { get; set; } @@ -50,6 +66,10 @@ public partial class LookupFilter [NotNull] private IStringLocalizer? Localizer { get; set; } + [Inject] + [NotNull] + private ILookupService? InjectLookupService { get; set; } + /// /// /// @@ -57,11 +77,6 @@ protected override void OnInitialized() { base.OnInitialized(); - if (Lookup == null) - { - throw new InvalidOperationException("the Parameter Lookup must be set."); - } - if (Type == null) { throw new InvalidOperationException("the Parameter Type must be set."); @@ -76,19 +91,28 @@ protected override void OnInitialized() /// /// /// - protected override void OnParametersSet() + protected override async Task OnParametersSetAsync() { - base.OnParametersSet(); + await base.OnParametersSetAsync(); - if (Items == null) - { - var items = new List + var items = new List { new("", Localizer["EnumFilter.AllText"].Value) }; + if (Lookup != null) + { items.AddRange(Lookup); - Items = items; } + else if (!string.IsNullOrEmpty(LookupServiceKey)) + { + var lookupService = LookupService ?? InjectLookupService; + var lookup = await lookupService.GetItemsAsync(LookupServiceKey, LookupServiceData); + if (lookup != null) + { + items.AddRange(lookup); + } + } + Items = items; } /// diff --git a/src/BootstrapBlazor/Components/Filters/TableFilter.razor b/src/BootstrapBlazor/Components/Filters/TableFilter.razor index fba1954b7e2..2e41bdef4cb 100644 --- a/src/BootstrapBlazor/Components/Filters/TableFilter.razor +++ b/src/BootstrapBlazor/Components/Filters/TableFilter.razor @@ -46,7 +46,7 @@ else } else if (IsLookup) { - + } else { diff --git a/src/BootstrapBlazor/Components/Filters/TableFilter.razor.cs b/src/BootstrapBlazor/Components/Filters/TableFilter.razor.cs index 4f6a5ba0739..87a17fea804 100644 --- a/src/BootstrapBlazor/Components/Filters/TableFilter.razor.cs +++ b/src/BootstrapBlazor/Components/Filters/TableFilter.razor.cs @@ -93,9 +93,7 @@ public partial class TableFilter : IFilter /// [Parameter] [NotNull] -#if NET6_0_OR_GREATER [EditorRequired] -#endif public ITableColumn? Column { get; set; } /// @@ -132,20 +130,11 @@ public partial class TableFilter : IFilter [NotNull] private IIconTheme? IconTheme { get; set; } - [Inject] - [NotNull] - private ILookupService? LookupService { get; set; } - /// /// 组件步长 /// private string? _step; - /// - /// 外键数据源集合 - /// - private Lazy?> _lookup = default!; - /// /// /// @@ -156,8 +145,6 @@ protected override void OnInitialized() _title = Column.GetDisplayName(); FieldKey = Column.GetFieldName(); Column.Filter = this; - - _lookup = new(() => Column.Lookup ?? LookupService.GetItemsByKey(Column.LookupServiceKey, Column.LookupServiceData)); _step = Column.Step; } diff --git a/src/BootstrapBlazor/Components/Table/InternalTableColumn.cs b/src/BootstrapBlazor/Components/Table/InternalTableColumn.cs index 33ea0f81d4a..e5e574d86fe 100644 --- a/src/BootstrapBlazor/Components/Table/InternalTableColumn.cs +++ b/src/BootstrapBlazor/Components/Table/InternalTableColumn.cs @@ -199,6 +199,11 @@ class InternalTableColumn(string fieldName, Type fieldType, string? fieldText = /// public object? LookupServiceData { get; set; } + /// + /// + /// + public ILookupService? LookupService { get; set; } + /// /// 获得/设置 单元格回调方法 /// diff --git a/src/BootstrapBlazor/Components/Table/Table.razor.cs b/src/BootstrapBlazor/Components/Table/Table.razor.cs index ec872e28adc..2921c619372 100644 --- a/src/BootstrapBlazor/Components/Table/Table.razor.cs +++ b/src/BootstrapBlazor/Components/Table/Table.razor.cs @@ -1141,6 +1141,12 @@ private async Task ProcessFirstRender() Columns.Clear(); Columns.AddRange(cols.OrderFunc()); + // 准备 Lookup 数据 + foreach (var column in Columns) + { + column.Lookup ??= await LookupService.GetItemsAsync(column.LookupServiceKey, column.LookupServiceData); + } + // 查看是否开启列宽序列化 _clientColumnWidths = await ReloadColumnWidthFromBrowserAsync(); ResetColumnWidth(); @@ -1323,7 +1329,7 @@ RenderFragment RenderTemplate() => col.Template == null : col.Template(item); RenderFragment RenderEditTemplate() => col.EditTemplate == null - ? new RenderFragment(builder => builder.CreateComponentByFieldType(this, col, item, changedType, false, LookupService)) + ? new RenderFragment(builder => builder.CreateComponentByFieldType(this, col, item, changedType, false)) : col.EditTemplate(item); } @@ -1365,7 +1371,7 @@ void SetDynamicEditTemplate() parameters.Add(new(nameof(ValidateBase.OnValueChanged), onValueChanged.Invoke(d, col, (model, column, val) => DynamicContext.OnValueChanged(model, column, val)))); col.ComponentParameters = parameters; } - builder.CreateComponentByFieldType(this, col, row, changedType, false, LookupService); + builder.CreateComponentByFieldType(this, col, row, changedType, false); }; } diff --git a/src/BootstrapBlazor/Components/Table/TableColumn.cs b/src/BootstrapBlazor/Components/Table/TableColumn.cs index f22969f9261..217091be838 100644 --- a/src/BootstrapBlazor/Components/Table/TableColumn.cs +++ b/src/BootstrapBlazor/Components/Table/TableColumn.cs @@ -457,6 +457,12 @@ public class TableColumn : BootstrapComponentBase, ITableColumn [Parameter] public StringComparison LookupStringComparison { get; set; } = StringComparison.OrdinalIgnoreCase; + /// + /// + /// + [Parameter] + public ILookupService? LookupService { get; set; } + /// /// > /// diff --git a/src/BootstrapBlazor/Extensions/ILookupServiceExtensions.cs b/src/BootstrapBlazor/Extensions/ILookupServiceExtensions.cs new file mode 100644 index 00000000000..c5a418362cb --- /dev/null +++ b/src/BootstrapBlazor/Extensions/ILookupServiceExtensions.cs @@ -0,0 +1,23 @@ +// 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; + +/// +/// 扩展方法"/> +/// +public static class ILookupServiceExtensions +{ + /// + /// 根据指定键值获取 Lookup 集合扩展方法,先调用同步方法,如果返回 null 则调用异步方法 + /// + /// + /// + /// + /// + public static async Task?> GetItemsAsync(this ILookupService service, string? key, object? data) => string.IsNullOrEmpty(key) + ? null + : service.GetItemsByKey(key, data) ?? await service.GetItemsByKeyAsync(key, data); +} diff --git a/src/BootstrapBlazor/Extensions/ITableColumnExtensions.cs b/src/BootstrapBlazor/Extensions/ITableColumnExtensions.cs index f79fd09085e..5e6c3538d9a 100644 --- a/src/BootstrapBlazor/Extensions/ITableColumnExtensions.cs +++ b/src/BootstrapBlazor/Extensions/ITableColumnExtensions.cs @@ -50,6 +50,7 @@ public static void CopyValue(this ITableColumn dest, IEditorItem source) if (source.LookupStringComparison != StringComparison.OrdinalIgnoreCase) dest.LookupStringComparison = source.LookupStringComparison; if (source.LookupServiceKey != null) dest.LookupServiceKey = source.LookupServiceKey; if (source.LookupServiceData != null) dest.LookupServiceData = source.LookupServiceData; + if (source.LookupService != null) dest.LookupService = source.LookupService; if (source.Readonly.HasValue) dest.Readonly = source.Readonly; if (source.Rows > 0) dest.Rows = source.Rows; if (source.SkipValidate) dest.SkipValidate = source.SkipValidate; diff --git a/src/BootstrapBlazor/Services/ILookupService.cs b/src/BootstrapBlazor/Services/ILookupService.cs index c684b490b66..119822e34ad 100644 --- a/src/BootstrapBlazor/Services/ILookupService.cs +++ b/src/BootstrapBlazor/Services/ILookupService.cs @@ -14,6 +14,8 @@ public interface ILookupService /// 根据指定键值获取 Lookup 集合方法 /// /// 获得 Lookup 数据集合键值 + [Obsolete("已弃用,请使用 data 参数重载方法;Deprecated, please use the data parameter method")] + [ExcludeFromCodeCoverage] IEnumerable? GetItemsByKey(string? key); /// @@ -22,4 +24,11 @@ public interface ILookupService /// 获得 Lookup 数据集合键值 /// Lookup 键值附加数据 IEnumerable? GetItemsByKey(string? key, object? data); + + /// + /// 根据指定键值获取 Lookup 集合异步方法 + /// + /// 获得 Lookup 数据集合键值 + /// Lookup 键值附加数据 + Task?> GetItemsByKeyAsync(string? key, object? data); } diff --git a/src/BootstrapBlazor/Services/LookupServiceBase.cs b/src/BootstrapBlazor/Services/LookupServiceBase.cs index 8b63958354e..7dbf912cfa5 100644 --- a/src/BootstrapBlazor/Services/LookupServiceBase.cs +++ b/src/BootstrapBlazor/Services/LookupServiceBase.cs @@ -13,10 +13,17 @@ public abstract class LookupServiceBase : ILookupService /// /// /// + [Obsolete("已弃用,请使用 data 参数重载方法;Deprecated, please use the data parameter method")] + [ExcludeFromCodeCoverage] public virtual IEnumerable? GetItemsByKey(string? key) => GetItemsByKey(key, null); /// /// /// public abstract IEnumerable? GetItemsByKey(string? key, object? data); + + /// + /// + /// + public virtual Task?> GetItemsByKeyAsync(string? key, object? data) => Task.FromResult(GetItemsByKey(key, data)); } diff --git a/src/BootstrapBlazor/Utils/Utility.cs b/src/BootstrapBlazor/Utils/Utility.cs index c4c7e5ab0c1..b00a0c2d07e 100644 --- a/src/BootstrapBlazor/Utils/Utility.cs +++ b/src/BootstrapBlazor/Utils/Utility.cs @@ -459,8 +459,7 @@ public static void CreateDisplayByFieldType(this RenderTreeBuilder builder, IEdi /// /// /// - /// - public static void CreateComponentByFieldType(this RenderTreeBuilder builder, ComponentBase component, IEditorItem item, object model, ItemChangedType changedType = ItemChangedType.Update, bool isSearch = false, ILookupService? lookUpService = null) + public static void CreateComponentByFieldType(this RenderTreeBuilder builder, ComponentBase component, IEditorItem item, object model, ItemChangedType changedType = ItemChangedType.Update, bool isSearch = false) { var fieldType = item.PropertyType; var fieldName = item.GetFieldName(); @@ -469,7 +468,7 @@ public static void CreateComponentByFieldType(this RenderTreeBuilder builder, Co var fieldValue = GenerateValue(model, fieldName); var fieldValueChanged = GenerateValueChanged(component, model, fieldName, fieldType); var valueExpression = GenerateValueExpression(model, fieldName, fieldType); - var lookup = item.Lookup ?? lookUpService?.GetItemsByKey(item.LookupServiceKey, item.LookupServiceData); + var lookup = item.Lookup; var componentType = item.ComponentType ?? GenerateComponentType(fieldType, item.Rows != 0, lookup); builder.OpenComponent(0, componentType); if (componentType.IsSubclassOf(typeof(ValidateBase<>).MakeGenericType(fieldType))) diff --git a/test/UnitTest/Attributes/AutoGenerateClassTest.cs b/test/UnitTest/Attributes/AutoGenerateClassTest.cs index 06a8271574c..f77ed200a4d 100644 --- a/test/UnitTest/Attributes/AutoGenerateClassTest.cs +++ b/test/UnitTest/Attributes/AutoGenerateClassTest.cs @@ -223,6 +223,9 @@ public void AutoGenerateColumn_Ok() attrEditor.IsPopover = true; Assert.True(attrEditor.IsPopover); + attrEditor.LookupService = new LookupService(); + Assert.NotNull(attrEditor.LookupService); + // 增加 GetDisplay 单元覆盖率 attr.Text = null; Assert.Equal(string.Empty, attr.GetDisplayName()); @@ -232,4 +235,9 @@ public void AutoGenerateColumn_Ok() Assert.True(attr.IsRequiredWhenAdd); Assert.Equal("test", attr.RequiredErrorMessage); } + + class LookupService : LookupServiceBase + { + public override IEnumerable? GetItemsByKey(string? key, object? data) => null; + } } diff --git a/test/UnitTest/Components/EditorFormTest.cs b/test/UnitTest/Components/EditorFormTest.cs index 38e340f47a5..120ccbee6eb 100644 --- a/test/UnitTest/Components/EditorFormTest.cs +++ b/test/UnitTest/Components/EditorFormTest.cs @@ -409,7 +409,7 @@ public void ColumnOrderCallback_Ok() }); }); var editor = cut.Instance; - var itemsField = editor.GetType().GetField("_formItems", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetField); + var itemsField = editor.GetType().GetProperty("RenderItems", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); Assert.NotNull(itemsField); var v = itemsField.GetValue(editor) as List; @@ -418,7 +418,7 @@ public void ColumnOrderCallback_Ok() } [Fact] - public void LookupServiceKey_Ok() + public async Task LookupServiceKey_Ok() { var foo = new Foo(); var cut = Context.RenderComponent>(pb => @@ -440,8 +440,40 @@ public void LookupServiceKey_Ok() }); var select = cut.FindComponent>(); var lookupService = Context.Services.GetRequiredService(); - var lookup = lookupService.GetItemsByKey("FooLookup"); - Assert.Equal(lookup!.Count(), select.Instance.Items.Count()); + var lookup = await lookupService.GetItemsAsync("FooLookup", ""); + Assert.NotNull(lookup); + Assert.Equal(lookup.Count(), select.Instance.Items.Count()); + } + + [Fact] + public async Task LookupService_Ok() + { + var lookupService = new FooLookupService(); + var foo = new Foo(); + var cut = Context.RenderComponent>(pb => + { + pb.Add(a => a.Model, foo); + pb.Add(a => a.AutoGenerateAllItem, false); + pb.Add(a => a.FieldItems, f => builder => + { + var index = 0; + builder.OpenComponent>(index++); + builder.AddAttribute(index++, nameof(EditorItem.Field), f.Name); + builder.AddAttribute(index++, nameof(EditorItem.FieldExpression), Utility.GenerateValueExpression(foo, nameof(Foo.Name), typeof(string))); + builder.AddAttribute(index++, nameof(EditorItem.Text), "Test-Text"); + builder.AddAttribute(index++, nameof(EditorItem.LookupService), lookupService); + builder.AddAttribute(index++, nameof(EditorItem.LookupServiceKey), "FooLookup"); + builder.AddAttribute(index++, nameof(EditorItem.LookupServiceData), true); + builder.AddAttribute(index++, nameof(EditorItem.LookupStringComparison), StringComparison.OrdinalIgnoreCase); + builder.CloseComponent(); + }); + }); + cut.WaitForElements(".select"); + var select = cut.FindComponent>(); + var lookup = await lookupService.GetItemsAsync("FooLookup", ""); + Assert.NotNull(lookup); + Assert.Equal(lookup.Count(), select.Instance.Items.Count()); + Assert.Equal("LookupService-Test-1-async", select.Instance.Items.First().Text); } [Theory] @@ -661,4 +693,26 @@ public async Task Test() } } } + + class FooLookupService : LookupServiceBase + { + public override IEnumerable? GetItemsByKey(string? key, object? data) => null; + + public override async Task?> GetItemsByKeyAsync(string? key, object? data) + { + await Task.Delay(300); + + IEnumerable? ret = null; + + if (key == "FooLookup") + { + ret = new SelectedItem[] + { + new("v1", "LookupService-Test-1-async"), + new("v2", "LookupService-Test-2-async") + }; + } + return ret; + } + } } diff --git a/test/UnitTest/Components/LayoutTest.cs b/test/UnitTest/Components/LayoutTest.cs index 9ee244f41ab..90631a12a16 100644 --- a/test/UnitTest/Components/LayoutTest.cs +++ b/test/UnitTest/Components/LayoutTest.cs @@ -52,7 +52,15 @@ public void IsFixedFooter_OK() [Fact] public void IsFixedTabHeader_OK() { - var cut = Context.RenderComponent(); + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.Side, new RenderFragment(builder => + { + builder.AddContent(0, "test"); + })); + pb.Add(a => a.Menus, new MenuItem[] { new() { Url = "/" } }); + + }); Assert.DoesNotContain("is-fixed-tab", cut.Markup); cut.SetParametersAndRender(pb => pb.Add(a => a.IsFixedTabHeader, true)); diff --git a/test/UnitTest/Components/TableColumnTest.cs b/test/UnitTest/Components/TableColumnTest.cs index 3c1ad2a7c1f..a4b48452c31 100644 --- a/test/UnitTest/Components/TableColumnTest.cs +++ b/test/UnitTest/Components/TableColumnTest.cs @@ -84,6 +84,7 @@ public void InternalTableColumn_Ok() SetValue("RequiredErrorMessage", "test"); SetValue("IsRequiredWhenAdd", true); SetValue("IsRequiredWhenEdit", true); + SetValue("LookupService", null); void SetValue(string propertyName, object? val) => type!.GetProperty(propertyName)!.SetValue(instance, val); } diff --git a/test/UnitTest/Components/TableLookupFilterTest.cs b/test/UnitTest/Components/TableLookupFilterTest.cs index 793ec8b5683..d89fbec0c85 100644 --- a/test/UnitTest/Components/TableLookupFilterTest.cs +++ b/test/UnitTest/Components/TableLookupFilterTest.cs @@ -7,6 +7,31 @@ namespace UnitTest.Components; public class TableLookupFilterTest : BootstrapBlazorTestBase { + [Fact] + public void Lookup_Ok() + { + var cut = Context.RenderComponent(pb => + { + pb.Add(a => a.Type, typeof(string)); + pb.Add(a => a.LookupServiceKey, "FooLookup"); + }); + var items = cut.FindAll(".dropdown-item"); + Assert.Equal(3, items.Count); + Assert.Contains("LookupService-Test-2", items[items.Count - 1].InnerHtml); + + var lookupService = Context.Services.GetKeyedService("FooLookupAsync"); + cut.SetParametersAndRender(pb => + { + pb.Add(a => a.LookupService, lookupService); + }); + cut.WaitForAssertion(() => + { + items = cut.FindAll(".dropdown-item"); + Assert.Equal(3, items.Count); + Assert.Contains("LookupService-Test-2-async", items[items.Count - 1].InnerHtml); + }); + } + [Fact] public void Reset_Ok() { diff --git a/test/UnitTest/Components/TableTest.cs b/test/UnitTest/Components/TableTest.cs index 48dfd02bff8..936168feafb 100644 --- a/test/UnitTest/Components/TableTest.cs +++ b/test/UnitTest/Components/TableTest.cs @@ -5004,6 +5004,7 @@ public void TableColumn_Property() builder.AddAttribute(30, "IsPopover", false); builder.AddAttribute(31, "IsVisibleWhenAdd", false); builder.AddAttribute(32, "IsVisibleWhenEdit", false); + builder.AddAttribute(33, "LookupService", new FooLookupService()); builder.CloseComponent(); }); }); @@ -5039,6 +5040,7 @@ public void TableColumn_Property() Assert.Equal(1, column.Instance.GroupOrder); Assert.True(column.Instance.ShowSearchWhenSelect); Assert.False(column.Instance.IsPopover); + Assert.NotNull(column.Instance.LookupService); var col = column.Instance as ITableColumn; Assert.NotNull(col.Template); @@ -8799,4 +8801,26 @@ public override int GetHashCode() return base.GetHashCode(); } } + + class FooLookupService : LookupServiceBase + { + public override IEnumerable? GetItemsByKey(string? key, object? data) => null; + + public override async Task?> GetItemsByKeyAsync(string? key, object? data) + { + await Task.Delay(300); + + IEnumerable? ret = null; + + if (key == "FooLookup") + { + ret = new SelectedItem[] + { + new("v1", "LookupService-Test-1-async"), + new("v2", "LookupService-Test-2-async") + }; + } + return ret; + } + } } diff --git a/test/UnitTest/Core/BootstrapBlazorTestBase.cs b/test/UnitTest/Core/BootstrapBlazorTestBase.cs index 3aac5664424..57d63fcda96 100644 --- a/test/UnitTest/Core/BootstrapBlazorTestBase.cs +++ b/test/UnitTest/Core/BootstrapBlazorTestBase.cs @@ -29,6 +29,7 @@ protected virtual void ConfigureServices(IServiceCollection services) op.IgnoreLocalizerMissing = false; }); services.AddSingleton(); + services.AddKeyedSingleton("FooLookupAsync"); } protected virtual void ConfigureConfiguration(IServiceCollection services) @@ -60,4 +61,26 @@ class FooLookupService : LookupServiceBase return ret; } } + + class FooLookupServiceAsync : LookupServiceBase + { + public override IEnumerable? GetItemsByKey(string? key, object? data) => null; + + public override async Task?> GetItemsByKeyAsync(string? key, object? data) + { + await Task.Delay(300); + + IEnumerable? ret = null; + + if (key == "FooLookup") + { + ret = new SelectedItem[] + { + new("v1", "LookupService-Test-1-async"), + new("v2", "LookupService-Test-2-async") + }; + } + return ret; + } + } } diff --git a/test/UnitTest/Extensions/ITableColumnExtensionsTest.cs b/test/UnitTest/Extensions/ITableColumnExtensionsTest.cs index dc59dde7965..91de55e0d53 100644 --- a/test/UnitTest/Extensions/ITableColumnExtensionsTest.cs +++ b/test/UnitTest/Extensions/ITableColumnExtensionsTest.cs @@ -53,6 +53,7 @@ public void CopyValue_Ok() Lookup = new List(), LookupStringComparison = StringComparison.Ordinal, LookupServiceKey = "test-key", + LookupService = new LookupService(), LookupServiceData = true, IsReadonlyWhenAdd = true, IsReadonlyWhenEdit = true, @@ -176,6 +177,10 @@ public void CopyValue_Ok() Assert.True(col.IsRequiredWhenEdit); Assert.True(col.IsRequiredWhenAdd); Assert.Equal("test", col.RequiredErrorMessage); + + Assert.NotNull(col.LookupService); + Assert.Equal("test-key", col.LookupServiceKey); + Assert.Equal(true, col.LookupServiceData); } [Fact] @@ -227,4 +232,9 @@ public void ToSearches_Ok() Assert.Equal(2, filters.Count(f => f.GetFilterConditions().FieldValue?.GetType() == typeof(double))); Assert.Equal(2, filters.Count(f => f.GetFilterConditions().FieldValue?.GetType() == typeof(decimal))); } + + class LookupService : LookupServiceBase + { + public override IEnumerable? GetItemsByKey(string? key, object? data) => null; + } } diff --git a/test/UnitTest/Services/LookupServiceTest.cs b/test/UnitTest/Services/LookupServiceTest.cs index 58c93e27250..6c1164a534f 100644 --- a/test/UnitTest/Services/LookupServiceTest.cs +++ b/test/UnitTest/Services/LookupServiceTest.cs @@ -5,12 +5,17 @@ namespace UnitTest.Services; -public class LookupServiceTest : BootstrapBlazorTestBase +public class LookupServiceTest { [Fact] public void LookupService_Null() { - var service = Context.Services.GetRequiredService(); - Assert.Null(service.GetItemsByKey("")); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddBootstrapBlazor(); + + var provider = serviceCollection.BuildServiceProvider(); + var service = provider.GetRequiredService(); + var data = service.GetItemsByKey("", ""); + Assert.Null(data); } }