Skip to content

Commit

Permalink
feat(Display): support LookupService method GetItemsByKeyAsync (#4924)
Browse files Browse the repository at this point in the history
* refactor: 重构代码

Co-Authored-By: chengKun <49547008+zhaijunlei955@users.noreply.github.com>

* refactor: 重构 Display 代码

更新 Lookup 逻辑

Co-Authored-By: chengKun <49547008+zhaijunlei955@users.noreply.github.com>

* test: 增加单元测试

---------

Co-Authored-By: chengKun <49547008+zhaijunlei955@users.noreply.github.com>
  • Loading branch information
ArgoZhang and zhaijunlei955 authored Dec 23, 2024
1 parent ece2207 commit 4350928
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 109 deletions.
4 changes: 2 additions & 2 deletions src/BootstrapBlazor/Components/Display/Display.razor
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
}
@if (ShowTooltip)
{
<Tooltip Title="@CurrentTextAsString">
<Tooltip Title="@_displayText">
@RenderContent
</Tooltip>
}
Expand All @@ -19,5 +19,5 @@ else

@code {
RenderFragment RenderContent =>
@<div @attributes="AdditionalAttributes" class="@ClassString">@CurrentTextAsString</div>;
@<div @attributes="AdditionalAttributes" class="@ClassString">@_displayText</div>;
}
155 changes: 49 additions & 106 deletions src/BootstrapBlazor/Components/Display/Display.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone

using System.Collections;
using System.Linq.Expressions;
using System.Reflection;

namespace BootstrapBlazor.Components;
Expand All @@ -18,10 +17,7 @@ public partial class Display<TValue>
.AddClassFromAttributes(AdditionalAttributes)
.Build();

/// <summary>
/// 获得 显示文本
/// </summary>
protected string? CurrentTextAsString { get; set; }
private string? _displayText;

/// <summary>
/// 获得/设置 异步格式化字符串
Expand Down Expand Up @@ -56,15 +52,20 @@ public partial class Display<TValue>
[Parameter]
public object? LookupServiceData { get; set; }

/// <summary>
/// <inheritdoc/>
/// </summary>
[Parameter]
public ILookupService? LookupService { get; set; }

[Inject]
[NotNull]
private ILookupService? LookupService { get; set; }
private ILookupService? InjectLookupService { get; set; }

/// <summary>
/// 获得/设置 类型解析回调方法 组件泛型为 Array 时内部调用
/// </summary>
[Parameter]

public Func<Assembly?, string, bool, Type?>? TypeResolver { get; set; }

/// <summary>
Expand All @@ -73,24 +74,6 @@ public partial class Display<TValue>
[Parameter]
public bool ShowTooltip { get; set; }

/// <summary>
/// <inheritdoc/>>
/// </summary>
/// <param name="parameters"></param>
/// <returns></returns>
public override Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);

if (!string.IsNullOrEmpty(LookupServiceKey))
{
Lookup ??= LookupService.GetItemsByKey(LookupServiceKey, LookupServiceData);
}

// For derived components, retain the usual lifecycle with OnInit/OnParametersSet/etc.
return base.SetParametersAsync(ParameterView.Empty);
}

/// <summary>
/// <inheritdoc/>>
/// </summary>
Expand All @@ -99,23 +82,23 @@ protected override async Task OnParametersSetAsync()
{
await base.OnParametersSetAsync();

CurrentTextAsString = await FormatTextAsString(Value);
_displayText = await FormatDisplayText(Value);
}

/// <summary>
/// 数值格式化委托方法
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private async Task<string?> FormatTextAsString(TValue value) => FormatterAsync != null
private async Task<string?> FormatDisplayText(TValue value) => FormatterAsync != null
? await FormatterAsync(value)
: (!string.IsNullOrEmpty(FormatString) && value != null
? Utility.Format(value, FormatString)
: value == null
? FormatValueString()
: FormatText(value));
: await FormatText(value));

private string FormatText([DisallowNull] TValue value)
private async Task<string> FormatText([DisallowNull] TValue value)
{
string ret;
var type = typeof(TValue);
Expand All @@ -125,12 +108,12 @@ private string FormatText([DisallowNull] TValue value)
}
else if (type.IsArray)
{
ret = ConvertArrayToString(value);
ret = ArrayConvertToString(value);
}
else if (type.IsGenericType && type.IsAssignableTo(typeof(IEnumerable)))
{
// 泛型集合 IEnumerable<TValue>
ret = ConvertEnumerableToString(value);
ret = await ConvertEnumerableToString(value);
}
else
{
Expand All @@ -152,91 +135,51 @@ private string FormatValueString()
return ret ?? valueString ?? string.Empty;
}

private Func<TValue, string>? _converterArray;
/// <summary>
/// 获取属性方法 Lambda 表达式
/// </summary>
/// <returns></returns>
private string ConvertArrayToString(TValue value)
private Func<TValue, string>? _arrayConvertoString;
private string ArrayConvertToString(TValue value)
{
return (_converterArray ??= ConvertArrayToStringLambda())(value);

Func<TValue, string> ConvertArrayToStringLambda()
{
Func<TValue, string> ret = _ => "";
var param = Expression.Parameter(typeof(Array));
var targetType = typeof(TValue).UnderlyingSystemType;
var methodType = ResolveArrayType();
if (methodType != null)
{
// 调用 string.Join<T>(",", IEnumerable<T>) 方法
var method = typeof(string).GetMethods().First(m => m is { Name: "Join", IsGenericMethod: true } && m.GetParameters()[0].ParameterType == typeof(string)).MakeGenericMethod(methodType);
var body = Expression.Call(method, Expression.Constant(","), Expression.Convert(param, targetType));
ret = Expression.Lambda<Func<TValue, string>>(body, param).Compile();
}
return ret;

Type? ResolveArrayType()
{
Type? t = null;
var typeName = targetType.FullName;
if (!string.IsNullOrEmpty(typeName))
{
typeName = typeName.Replace("[]", "");
if (typeName.Contains('+'))
{
typeName = typeName.Split('+', StringSplitOptions.RemoveEmptyEntries).Last();
}
t = Type.GetType(typeName, null, TypeResolver, false, true);
}
return t;
}
}
_arrayConvertoString ??= LambdaExtensions.ArrayConvertToStringLambda<TValue>(TypeResolver).Compile();
return _arrayConvertoString(value);
}

private static Func<TValue, string>? _convertEnumerableToString;
private static Func<TValue, IEnumerable<string>>? _convertToEnumerableString;
/// <summary>
/// 获取属性方法 Lambda 表达式
/// </summary>
/// <returns></returns>
private string ConvertEnumerableToString(TValue value)
private static Func<TValue, string>? _enumerableConvertToString;
private async Task<string> ConvertEnumerableToString(TValue value)
{
return Lookup == null
? (_convertEnumerableToString ??= ConvertEnumerableToStringLambda())(value)
: GetTextByValue((_convertToEnumerableString ??= ConvertToEnumerableStringLambda())(value));

static Func<TValue, string> ConvertEnumerableToStringLambda()
{
var typeArguments = typeof(TValue).GenericTypeArguments;
var param = Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(typeArguments));
var method = typeof(string).GetMethods().First(m => m is { Name: "Join", IsGenericMethod: true } && m.GetParameters()[0].ParameterType == typeof(string)).MakeGenericMethod(typeArguments);
var body = Expression.Call(method, Expression.Constant(","), param);
return Expression.Lambda<Func<TValue, string>>(body, param).Compile();
}
_enumerableConvertToString ??= LambdaExtensions.EnumerableConvertToStringLambda<TValue>().Compile();
var lookup = await GetLookup();
return lookup == null
? _enumerableConvertToString(value)
: GetTextByValue(lookup, value);
}

static Func<TValue, IEnumerable<string>> ConvertToEnumerableStringLambda()
private static Func<TValue, IEnumerable<string>>? _convertToStringEnumerable;
private static string GetTextByValue(IEnumerable<SelectedItem> lookup, TValue value)
{
_convertToStringEnumerable ??= LambdaExtensions.ConvertToStringEnumerableLambda<TValue>().Compile();
var source = _convertToStringEnumerable(value);
return string.Join(",", source.Aggregate(new List<string>(), (s, i) =>
{
var typeArguments = typeof(TValue).GenericTypeArguments;
var param = Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(typeArguments));

var method = typeof(Display<>).MakeGenericType(typeof(TValue))
.GetMethod(nameof(Cast), BindingFlags.NonPublic | BindingFlags.Static)!
.MakeGenericMethod(typeArguments);
var body = Expression.Call(method, param);
return Expression.Lambda<Func<TValue, IEnumerable<string>>>(body, param).Compile();
}
var text = lookup.FirstOrDefault(d => d.Value.Equals(i, StringComparison.OrdinalIgnoreCase))?.Text;
if (text != null)
{
s.Add(text);
}
return s;
}));
}

private static IEnumerable<string> Cast<TType>(IEnumerable<TType> source) => source.Select(i => i?.ToString() ?? string.Empty);
private ILookupService GetLookupService() => LookupService ?? InjectLookupService;

private string GetTextByValue(IEnumerable<string> source) => string.Join(",", source.Aggregate(new List<string>(), (s, i) =>
private IEnumerable<SelectedItem>? _lookupData;
private async Task<IEnumerable<SelectedItem>?> GetLookup()
{
var text = Lookup!.FirstOrDefault(d => d.Value.Equals(i, StringComparison.OrdinalIgnoreCase))?.Text;
if (text != null)
if (Lookup != null)
{
s.Add(text);
return Lookup;
}
return s;
}));

var lookupService = GetLookupService();
_lookupData ??= await lookupService.GetItemsAsync(LookupServiceKey, LookupServiceData);
return _lookupData;
}
}
4 changes: 3 additions & 1 deletion src/BootstrapBlazor/Components/Filters/LookupFilter.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ protected override async Task OnParametersSetAsync()
}
else if (!string.IsNullOrEmpty(LookupServiceKey))
{
var lookupService = LookupService ?? InjectLookupService;
var lookupService = GetLookupService();
var lookup = await lookupService.GetItemsAsync(LookupServiceKey, LookupServiceData);
if (lookup != null)
{
Expand All @@ -115,6 +115,8 @@ protected override async Task OnParametersSetAsync()
Items = items;
}

private ILookupService GetLookupService() => LookupService ?? InjectLookupService;

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand Down
74 changes: 74 additions & 0 deletions src/BootstrapBlazor/Extensions/LambdaExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -817,4 +817,78 @@ public static Expression<Func<TModel, TValue>> GetKeyValue<TModel, TValue>(Type?
}
return ret;
}

/// <summary>
/// 数组转成字符串表达式
/// </summary>
/// <typeparam name="TValue"></typeparam>
/// <returns></returns>
/// <remarks><code><![CDATA[string.Join<T>(",", IEnumerable<T>)]]></code></remarks>
public static Expression<Func<TValue, string>> EnumerableConvertToStringLambda<TValue>()
{
var typeArguments = typeof(TValue).GenericTypeArguments;
var param = Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(typeArguments));

var method = typeof(string).GetMethods().First(m => m is { Name: "Join", IsGenericMethod: true } && m.GetParameters()[0].ParameterType == typeof(string)).MakeGenericMethod(typeArguments);
var body = Expression.Call(method, Expression.Constant(","), param);
return Expression.Lambda<Func<TValue, string>>(body, param);
}

/// <summary>
/// 泛型集合转换成 <![CDATA[IEnumerable<string>]]> 方法
/// </summary>
/// <typeparam name="TValue"></typeparam>
/// <remarks><code><![CDATA[IEnumerable<T>]]> to <![CDATA[IEnumerable<string>]]></code></remarks>
/// <returns></returns>
public static Expression<Func<TValue, IEnumerable<string>>> ConvertToStringEnumerableLambda<TValue>()
{
var typeArguments = typeof(TValue).GenericTypeArguments;
var param = Expression.Parameter(typeof(IEnumerable<>).MakeGenericType(typeArguments));

var method = typeof(LambdaExtensions)
.GetMethod(nameof(Cast), BindingFlags.NonPublic | BindingFlags.Static)!
.MakeGenericMethod(typeArguments);
var body = Expression.Call(method, param);
return Expression.Lambda<Func<TValue, IEnumerable<string>>>(body, param);
}

private static IEnumerable<string> Cast<TType>(IEnumerable<TType> source) => source.Select(i => i?.ToString() ?? string.Empty);

/// <summary>
/// 数组转成字符串表达式
/// </summary>
/// <typeparam name="TValue"></typeparam>
/// <param name="typeResolver"></param>
/// <remarks><code><![CDATA[string.Join<T>(",", Array)]]></code></remarks>
public static Expression<Func<TValue, string>> ArrayConvertToStringLambda<TValue>(Func<Assembly?, string, bool, Type?>? typeResolver)
{
Expression<Func<TValue, string>> ret = _ => "";
var param = Expression.Parameter(typeof(Array));
var targetType = typeof(TValue).UnderlyingSystemType;
var methodType = ResolveArrayType(targetType, typeResolver);
if (methodType != null)
{
// 调用 string.Join<T>(",", IEnumerable<T>) 方法
var method = typeof(string).GetMethods().First(m => m is { Name: "Join", IsGenericMethod: true } && m.GetParameters()[0].ParameterType == typeof(string)).MakeGenericMethod(methodType);
var body = Expression.Call(method, Expression.Constant(","), Expression.Convert(param, targetType));
ret = Expression.Lambda<Func<TValue, string>>(body, param);
}
return ret;
}

private static Type? ResolveArrayType(Type targetType, Func<Assembly?, string, bool, Type?>? typeResolver)
{
Type? t = null;
var typeName = targetType.FullName;
if (!string.IsNullOrEmpty(typeName))
{
typeName = typeName.Replace("[]", "");
if (typeName.Contains('+'))
{
typeName = typeName.Split('+', StringSplitOptions.RemoveEmptyEntries).Last();
}
t = Type.GetType(typeName, null, typeResolver, false, true);
}
return t;
}
}
1 change: 1 addition & 0 deletions test/UnitTest/Components/DisplayTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public void LookupService_Ok()
{
var cut = Context.RenderComponent<Display<List<string>>>(pb =>
{
pb.Add(a => a.LookupService, null);
pb.Add(a => a.LookupServiceKey, "FooLookup");
pb.Add(a => a.LookupServiceData, true);
pb.Add(a => a.Value, ["v1", "v2"]);
Expand Down

0 comments on commit 4350928

Please sign in to comment.