mirror of
https://github.com/dotnetcore/BootstrapBlazor.git
synced 2025-12-20 10:26:41 +08:00
refactor(CacheManager): remove some cache item (#5161)
* feat(ICacheManager): add Keys parameter * test: 更新单元测试 * feat: add cache list page * refactor: 移除 GetStringLocalizerFromService 缓存 * doc: 增加删除所有缓存键值按钮 * refactor: 重构代码 * doc: 增加 CacheList 页面 * doc: 格式化水印文档 * doc: 增加缓存值列 * refactor: 更新值 * doc: 增加缓存项统计 * refactor: 移除 GetPlaceholder 缓存 * refactor: 移除 TryGetProperty 缓存 * doc: 更新资源文件 * refactor GetDisplayName 移除缓存 * refactor:获得属性私有字段方法移除缓存 * refactor: 弃用 ReloadOnChange 参数 * refactor: 更新 GetJsonStringFromAssembly 逻辑 * refactor: GetRange 移除缓存 * refactor: 精简 Lambda 表达式缓存 * refactor: 移除 DEBUG 模式下缓存设置 * refactor: 移除 SetSlidingExpirationByType 扩展方法 * refactor: 移除非 JsonStringLocalizer 缓存未找到键值缓存逻辑 * refactor: 代码重构减少函数调用 Co-Authored-By: Alex chow <zhouchuanglin@gmail.com>
This commit is contained in:
37
src/BootstrapBlazor.Server/Components/Pages/CacheList.razor
Normal file
37
src/BootstrapBlazor.Server/Components/Pages/CacheList.razor
Normal file
@@ -0,0 +1,37 @@
|
||||
@page "/cache-list"
|
||||
|
||||
<h3>@Localizer["CacheListTitle"]</h3>
|
||||
|
||||
<h4>@((MarkupString)Localizer["CacheListIntro"].Value)</h4>
|
||||
|
||||
<div class="table-cache-list">
|
||||
<Table Items="_cacheList" IsBordered="true" IsStriped="true" IsFixedHeader="true"
|
||||
ShowToolbar="true" ShowAddButton="false" ShowEditButton="false" ShowDeleteButton="false" ShowRefresh="false">
|
||||
<TableColumns>
|
||||
<TableTemplateColumn Text="@Localizer["CacheListKey"]" TextWrap="true">
|
||||
<Template Context="v">
|
||||
@v.Row.ToString()
|
||||
</Template>
|
||||
</TableTemplateColumn>
|
||||
<TableTemplateColumn Text="@Localizer["CacheListValue"]" TextWrap="true" Width="220">
|
||||
<Template Context="v">
|
||||
@GetValue(v.Row)
|
||||
</Template>
|
||||
</TableTemplateColumn>
|
||||
<TableTemplateColumn Text="@Localizer["CacheListAction"]" Width="80">
|
||||
<Template Context="v">
|
||||
<Button Size="Size.ExtraSmall" Color="Color.Danger" OnClick="() => OnDelete(v.Row)" Icon="fa-solid fa-xmark" Text="@Localizer["CacheListDelete"]"></Button>
|
||||
</Template>
|
||||
</TableTemplateColumn>
|
||||
</TableColumns>
|
||||
<TableToolbarBeforeTemplate>
|
||||
<Button Text="@Localizer["CacheListDeleteAll"]" Icon="fa-solid fa-xmark" OnClick="OnDeleteAll"></Button>
|
||||
</TableToolbarBeforeTemplate>
|
||||
<TableExtensionToolbarTemplate>
|
||||
<Button Text="@Localizer["CacheListRefresh"]" Icon="fa-solid fa-refresh" OnClick="OnRefresh"></Button>
|
||||
</TableExtensionToolbarTemplate>
|
||||
</Table>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
@Localizer["CacheListCount", CacheManager.Count]
|
||||
</div>
|
||||
@@ -0,0 +1,78 @@
|
||||
// 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
|
||||
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using System.Collections;
|
||||
|
||||
namespace BootstrapBlazor.Server.Components.Pages;
|
||||
|
||||
/// <summary>
|
||||
/// CacheManager 管理组件
|
||||
/// </summary>
|
||||
public partial class CacheList
|
||||
{
|
||||
[Inject, NotNull]
|
||||
private ICacheManager? CacheManager { get; set; }
|
||||
|
||||
[Inject, NotNull]
|
||||
private IStringLocalizer<CacheList>? Localizer { get; set; }
|
||||
|
||||
private List<object>? _cacheList;
|
||||
|
||||
/// <summary>
|
||||
/// <inheritdoc/>
|
||||
/// </summary>
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
base.OnInitialized();
|
||||
UpdateCacheList();
|
||||
}
|
||||
|
||||
private void OnDelete(object key)
|
||||
{
|
||||
CacheManager.Clear(key);
|
||||
UpdateCacheList();
|
||||
}
|
||||
|
||||
private void OnDeleteAll()
|
||||
{
|
||||
CacheManager.Clear();
|
||||
UpdateCacheList();
|
||||
}
|
||||
|
||||
private void OnRefresh()
|
||||
{
|
||||
UpdateCacheList();
|
||||
}
|
||||
|
||||
private void UpdateCacheList()
|
||||
{
|
||||
_cacheList = CacheManager.Keys.OrderBy(i => i.ToString()).ToList();
|
||||
}
|
||||
|
||||
private string GetValue(object key)
|
||||
{
|
||||
string ret = "-";
|
||||
if (CacheManager.TryGetValue(key, out object? value))
|
||||
{
|
||||
if (value is string stringValue)
|
||||
{
|
||||
ret = stringValue;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (value is IEnumerable)
|
||||
{
|
||||
ret = $"{LambdaExtensions.ElementCount(value)}";
|
||||
}
|
||||
else
|
||||
{
|
||||
ret = value?.ToString() ?? "-";
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.table-cache-list {
|
||||
height: calc(100vh - 180px);
|
||||
}
|
||||
@@ -3,38 +3,38 @@
|
||||
|
||||
<h3>@Localizer["WatermarkTitle"]</h3>
|
||||
|
||||
<h4>@Localizer["Watermarkntro"]</h4>
|
||||
<h4>@Localizer["WatermarkIntro"]</h4>
|
||||
|
||||
<DemoBlock Title="@Localizer["WatermarkNormalTitle"]" Introduction="@Localizer["WatermarkNormalIntro"]" Name="Normal">
|
||||
<section ignore>
|
||||
<div class="row form-inline g-3">
|
||||
<div class="col-12 col-sm-6">
|
||||
<BootstrapInputGroup>
|
||||
<BootstrapInputGroupLabel DisplayText="Text"></BootstrapInputGroupLabel>
|
||||
<BootstrapInputGroupLabel DisplayText="Text" Width="76"></BootstrapInputGroupLabel>
|
||||
<BootstrapInput @bind-Value="@_text"></BootstrapInput>
|
||||
</BootstrapInputGroup>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6">
|
||||
<BootstrapInputGroup>
|
||||
<BootstrapInputGroupLabel DisplayText="FontSize"></BootstrapInputGroupLabel>
|
||||
<BootstrapInputGroupLabel DisplayText="FontSize" Width="76"></BootstrapInputGroupLabel>
|
||||
<Slider @bind-Value="@_fontSize" Min="12" Max="20"></Slider>
|
||||
</BootstrapInputGroup>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6">
|
||||
<BootstrapInputGroup>
|
||||
<BootstrapInputGroupLabel DisplayText="Color"></BootstrapInputGroupLabel>
|
||||
<BootstrapInputGroupLabel DisplayText="Color" Width="76"></BootstrapInputGroupLabel>
|
||||
<ColorPicker @bind-Value="@_color" IsSupportOpacity="true"></ColorPicker>
|
||||
</BootstrapInputGroup>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6">
|
||||
<BootstrapInputGroup>
|
||||
<BootstrapInputGroupLabel DisplayText="Rotate"></BootstrapInputGroupLabel>
|
||||
<BootstrapInputGroupLabel DisplayText="Rotate" Width="76"></BootstrapInputGroupLabel>
|
||||
<Slider @bind-Value="@_rotate" Min="-180" Max="180"></Slider>
|
||||
</BootstrapInputGroup>
|
||||
</div>
|
||||
<div class="col-12 col-sm-6">
|
||||
<BootstrapInputGroup>
|
||||
<BootstrapInputGroupLabel DisplayText="Gap"></BootstrapInputGroupLabel>
|
||||
<BootstrapInputGroupLabel DisplayText="Gap" Width="76"></BootstrapInputGroupLabel>
|
||||
<Slider @bind-Value="@_gap" Min="0" Max="100"></Slider>
|
||||
</BootstrapInputGroup>
|
||||
</div>
|
||||
|
||||
@@ -6881,12 +6881,11 @@
|
||||
"AffixNormalTitle": "Basic usage",
|
||||
"AffixNormalIntro": "Affix is fixed at the top of the page by default",
|
||||
"AffixPositionTitle": "Position",
|
||||
"AffixPositionIntro": "Use the parameter <code>Position</code> to control whether the top or bottom is fixed",
|
||||
"AffixOffsetDesc": "The parameter <code>Position</code> controls whether the top or bottom is fixed, and the <code>Offset</code> value sets the offset to the top or bottom"
|
||||
"AffixPositionIntro": "The parameter <code>Position</code> controls whether the top or bottom is fixed, and the <code>Offset</code> value sets the offset to the top or bottom"
|
||||
},
|
||||
"BootstrapBlazor.Server.Components.Samples.Watermarks": {
|
||||
"WatermarkTitle": "Watermark",
|
||||
"Watermarkntro": "Add specific text or patterns to the page",
|
||||
"WatermarkIntro": "Add specific text or patterns to the page",
|
||||
"WatermarkNormalTitle": "Basic usage",
|
||||
"WatermarkNormalIntro": "Use the <code>Text</code> property to set a string to specify the watermark text"
|
||||
},
|
||||
@@ -6901,5 +6900,16 @@
|
||||
"Name": "Name",
|
||||
"Description": "Description",
|
||||
"Type": "Type"
|
||||
},
|
||||
"BootstrapBlazor.Server.Components.Pages.CacheList": {
|
||||
"CacheListTitle": "CacheManager",
|
||||
"CacheListIntro": "Manage the component library internal cache through the <code>ICacheManager</code> interface method",
|
||||
"CacheListKey": "Key",
|
||||
"CacheListValue": "Value",
|
||||
"CacheListAction": "Action",
|
||||
"CacheListRefresh": "Refresh",
|
||||
"CacheListDelete": "Delete",
|
||||
"CacheListDeleteAll": "Clear",
|
||||
"CacheListCount": "Total {0} Entry"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6885,7 +6885,7 @@
|
||||
},
|
||||
"BootstrapBlazor.Server.Components.Samples.Watermarks": {
|
||||
"WatermarkTitle": "Watermark 水印组件",
|
||||
"Watermarkntro": "在页面上添加文本或图片等水印信息",
|
||||
"WatermarkIntro": "在页面上添加文本或图片等水印信息",
|
||||
"WatermarkNormalTitle": "基础用法",
|
||||
"WatermarkNormalIntro": "使用 <code>Text</code> 属性设置一个字符串指定水印内容"
|
||||
},
|
||||
@@ -6900,5 +6900,16 @@
|
||||
"Name": "参数",
|
||||
"Description": "说明",
|
||||
"Type": "类型"
|
||||
},
|
||||
"BootstrapBlazor.Server.Components.Pages.CacheList": {
|
||||
"CacheListTitle": "缓存管理",
|
||||
"CacheListIntro": "通过 <code>ICacheManager</code> 接口方法管理组件库内部缓存",
|
||||
"CacheListKey": "键",
|
||||
"CacheListValue": "值",
|
||||
"CacheListAction": "操作",
|
||||
"CacheListRefresh": "刷新",
|
||||
"CacheListDelete": "删除",
|
||||
"CacheListDeleteAll": "清除全部",
|
||||
"CacheListCount": "共 {0} 个键值"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
// 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
|
||||
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
|
||||
namespace BootstrapBlazor.Components;
|
||||
|
||||
/// <summary>
|
||||
/// ICacheEntry 扩展类
|
||||
/// </summary>
|
||||
public static class ICacheEntryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 设置 动态程序集滑动过期时间 10 秒
|
||||
/// </summary>
|
||||
/// <param name="entry"></param>
|
||||
/// <param name="type"></param>
|
||||
/// <param name="offset">默认 null 内部设置为 10 秒</param>
|
||||
public static void SetSlidingExpirationByType(this ICacheEntry entry, Type type, TimeSpan? offset = null)
|
||||
{
|
||||
offset ??= type.Assembly.IsDynamic ? TimeSpan.FromSeconds(10) : TimeSpan.FromMinutes(5);
|
||||
entry.SlidingExpiration = offset.Value;
|
||||
}
|
||||
}
|
||||
@@ -28,13 +28,6 @@ internal static class LocalizationOptionsExtensions
|
||||
|
||||
// 获取程序集中的资源文件
|
||||
var assemblies = new List<Assembly>() { assembly };
|
||||
|
||||
var entryAssembly = GetAssembly();
|
||||
if (assembly != entryAssembly)
|
||||
{
|
||||
assemblies.Add(entryAssembly);
|
||||
}
|
||||
|
||||
if (option.AdditionalJsonAssemblies != null)
|
||||
{
|
||||
assemblies.AddRange(option.AdditionalJsonAssemblies);
|
||||
@@ -58,7 +51,7 @@ internal static class LocalizationOptionsExtensions
|
||||
});
|
||||
foreach (var file in files)
|
||||
{
|
||||
builder.AddJsonFile(file, true, option.ReloadOnChange);
|
||||
builder.AddJsonFile(file, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,9 +65,6 @@ internal static class LocalizationOptionsExtensions
|
||||
}
|
||||
|
||||
return config.GetChildren();
|
||||
|
||||
[ExcludeFromCodeCoverage]
|
||||
Assembly GetAssembly() => Assembly.GetEntryAssembly() ?? assembly;
|
||||
}
|
||||
|
||||
private static List<Stream> GetResourceStream(this JsonLocalizationOptions option, Assembly assembly, string cultureName)
|
||||
|
||||
@@ -12,9 +12,9 @@ namespace BootstrapBlazor.Components;
|
||||
|
||||
internal static class TypeExtensions
|
||||
{
|
||||
public static PropertyInfo? GetPropertyByName(this Type type, string propertyName) => CacheManager.GetRuntimeProperties(type).Find(p => p.Name == propertyName);
|
||||
public static PropertyInfo? GetPropertyByName(this Type type, string propertyName) => type.GetRuntimeProperties().FirstOrDefault(p => p.Name == propertyName);
|
||||
|
||||
public static FieldInfo? GetFieldByName(this Type type, string fieldName) => CacheManager.GetRuntimeFields(type).Find(p => p.Name == fieldName);
|
||||
public static FieldInfo? GetFieldByName(this Type type, string fieldName) => type.GetRuntimeFields().FirstOrDefault(p => p.Name == fieldName);
|
||||
|
||||
public static async Task<bool> IsAuthorizedAsync(this Type type, IServiceProvider serviceProvider, Task<AuthenticationState>? authenticateState, object? resource = null)
|
||||
{
|
||||
@@ -46,9 +46,8 @@ internal static class TypeExtensions
|
||||
// It's not meaningful to specify a nonempty scheme, since by the time Components
|
||||
// authorization runs, we already have a specific ClaimsPrincipal (we're stateful).
|
||||
// To avoid any confusion, ensure the developer isn't trying to specify a scheme.
|
||||
for (var i = 0; i < authorizeData.Length; i++)
|
||||
foreach (var entry in authorizeData)
|
||||
{
|
||||
var entry = authorizeData[i];
|
||||
if (!string.IsNullOrEmpty(entry.AuthenticationSchemes))
|
||||
{
|
||||
throw new NotSupportedException($"The authorization data specifies an authentication scheme with value '{entry.AuthenticationSchemes}'. Authentication schemes cannot be specified for components.");
|
||||
|
||||
@@ -51,6 +51,8 @@ public class JsonLocalizationOptions : LocalizationOptions
|
||||
/// <summary>
|
||||
/// 获得/设置 资源文件是否热加载 默认 false
|
||||
/// </summary>
|
||||
[Obsolete("已弃用 Deprecated")]
|
||||
[ExcludeFromCodeCoverage]
|
||||
public bool ReloadOnChange { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -81,28 +81,35 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
|
||||
var localizer = Utility.GetStringLocalizerFromService(Assembly, typeName);
|
||||
if (localizer != null && localizer is not JsonStringLocalizer)
|
||||
{
|
||||
var cacheKey = $"name={name}&culture={CultureInfo.CurrentUICulture.Name}";
|
||||
if (!_missingManifestCache.ContainsKey(cacheKey))
|
||||
var l = localizer[name];
|
||||
if (!l.ResourceNotFound)
|
||||
{
|
||||
var l = localizer[name];
|
||||
if (!l.ResourceNotFound)
|
||||
{
|
||||
ret = l.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleMissingResourceItem(name);
|
||||
}
|
||||
ret = l.Value;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<string, object?> _missingManifestCache = [];
|
||||
private string? GetStringSafelyFromJson(string name)
|
||||
{
|
||||
// get string from json localization file
|
||||
var localizerStrings = MegerResolveLocalizers(CacheManager.GetAllStringsByTypeName(Assembly, typeName));
|
||||
return GetValueFromCache(localizerStrings, name);
|
||||
var cacheKey = $"name={name}&culture={CultureInfo.CurrentUICulture.Name}";
|
||||
string? ret = null;
|
||||
if (!_missingManifestCache.ContainsKey(cacheKey))
|
||||
{
|
||||
var l = localizerStrings.Find(i => i.Name == name);
|
||||
if (l is { ResourceNotFound: false })
|
||||
{
|
||||
ret = l.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleMissingResourceItem(name);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private List<LocalizedString> MegerResolveLocalizers(IEnumerable<LocalizedString>? localizerStrings)
|
||||
@@ -118,26 +125,6 @@ internal class JsonStringLocalizer(Assembly assembly, string typeName, string ba
|
||||
return localizers;
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<string, object?> _missingManifestCache = [];
|
||||
|
||||
private string? GetValueFromCache(List<LocalizedString> localizerStrings, string name)
|
||||
{
|
||||
string? ret = null;
|
||||
var cacheKey = $"name={name}&culture={CultureInfo.CurrentUICulture.Name}";
|
||||
if (!_missingManifestCache.ContainsKey(cacheKey))
|
||||
{
|
||||
var l = localizerStrings.Find(i => i.Name == name);
|
||||
if (l is { ResourceNotFound: false })
|
||||
{
|
||||
ret = l.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleMissingResourceItem(name);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void HandleMissingResourceItem(string name)
|
||||
{
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
|
||||
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using System.ComponentModel;
|
||||
@@ -48,10 +47,6 @@ internal class CacheManager : ICacheManager
|
||||
/// </summary>
|
||||
public TItem GetOrCreate<TItem>(object key, Func<ICacheEntry, TItem> factory) => Cache.GetOrCreate(key, entry =>
|
||||
{
|
||||
#if DEBUG
|
||||
entry.SlidingExpiration = TimeSpan.FromSeconds(5);
|
||||
#endif
|
||||
|
||||
if (key is not string)
|
||||
{
|
||||
entry.SetSlidingExpiration(TimeSpan.FromMinutes(5));
|
||||
@@ -64,10 +59,6 @@ internal class CacheManager : ICacheManager
|
||||
/// </summary>
|
||||
public Task<TItem> GetOrCreateAsync<TItem>(object key, Func<ICacheEntry, Task<TItem>> factory) => Cache.GetOrCreateAsync(key, async entry =>
|
||||
{
|
||||
#if DEBUG
|
||||
entry.SlidingExpiration = TimeSpan.FromSeconds(5);
|
||||
#endif
|
||||
|
||||
if (key is not string)
|
||||
{
|
||||
entry.SetSlidingExpiration(TimeSpan.FromMinutes(5));
|
||||
@@ -183,7 +174,6 @@ internal class CacheManager : ICacheManager
|
||||
var cacheKey = $"Lambda-Count-{type.GetUniqueTypeName()}";
|
||||
var invoker = Instance.GetOrCreate(cacheKey, entry =>
|
||||
{
|
||||
entry.SetSlidingExpirationByType(type);
|
||||
return LambdaExtensions.CountLambda(type).Compile();
|
||||
});
|
||||
ret = invoker(value);
|
||||
@@ -218,10 +208,12 @@ internal class CacheManager : ICacheManager
|
||||
/// <param name="assembly">Assembly 程序集实例</param>
|
||||
/// <param name="typeName">类型名称</param>
|
||||
/// <returns></returns>
|
||||
public static IStringLocalizer? GetStringLocalizerFromService(Assembly assembly, string typeName) => assembly.IsDynamic
|
||||
? null
|
||||
: Instance.GetOrCreate($"{nameof(GetStringLocalizerFromService)}-{CultureInfo.CurrentUICulture.Name}-{assembly.GetUniqueName()}-{typeName}", _ =>
|
||||
public static IStringLocalizer? GetStringLocalizerFromService(Assembly assembly, string typeName)
|
||||
{
|
||||
if (assembly.IsDynamic)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
IStringLocalizer? ret = null;
|
||||
var factories = Instance.Provider.GetServices<IStringLocalizerFactory>();
|
||||
var factory = factories.LastOrDefault(a => a is not JsonStringLocalizerFactory);
|
||||
@@ -234,7 +226,7 @@ internal class CacheManager : ICacheManager
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定文化本地化资源集合
|
||||
@@ -307,51 +299,42 @@ internal class CacheManager : ICacheManager
|
||||
/// <returns></returns>
|
||||
public static string GetDisplayName(Type modelType, string fieldName)
|
||||
{
|
||||
var cacheKey = $"{nameof(GetDisplayName)}-{CultureInfo.CurrentUICulture.Name}-{modelType.GetUniqueTypeName()}-{fieldName}";
|
||||
var displayName = Instance.GetOrCreate(cacheKey, entry =>
|
||||
string? dn = null;
|
||||
// 显示名称为空时通过资源文件查找 FieldName 项
|
||||
var localizer = modelType.Assembly.IsDynamic ? null : CreateLocalizerByType(modelType);
|
||||
var stringLocalizer = localizer?[fieldName];
|
||||
if (stringLocalizer is { ResourceNotFound: false })
|
||||
{
|
||||
string? dn = null;
|
||||
// 显示名称为空时通过资源文件查找 FieldName 项
|
||||
var localizer = modelType.Assembly.IsDynamic ? null : CreateLocalizerByType(modelType);
|
||||
var stringLocalizer = localizer?[fieldName];
|
||||
if (stringLocalizer is { ResourceNotFound: false })
|
||||
{
|
||||
dn = stringLocalizer.Value;
|
||||
}
|
||||
else if (modelType.IsEnum)
|
||||
{
|
||||
var info = modelType.GetFieldByName(fieldName);
|
||||
if (info != null)
|
||||
{
|
||||
dn = FindDisplayAttribute(info);
|
||||
}
|
||||
}
|
||||
else if (TryGetProperty(modelType, fieldName, out var propertyInfo))
|
||||
{
|
||||
dn = FindDisplayAttribute(propertyInfo);
|
||||
}
|
||||
|
||||
entry.SetSlidingExpirationByType(modelType);
|
||||
|
||||
return dn;
|
||||
});
|
||||
|
||||
return displayName ?? fieldName;
|
||||
|
||||
string? FindDisplayAttribute(MemberInfo memberInfo)
|
||||
{
|
||||
// 回退查找 Display 标签
|
||||
var dn = memberInfo.GetCustomAttribute<DisplayAttribute>(true)?.Name
|
||||
?? memberInfo.GetCustomAttribute<DisplayNameAttribute>(true)?.DisplayName
|
||||
?? memberInfo.GetCustomAttribute<DescriptionAttribute>(true)?.Description;
|
||||
|
||||
// 回退查找资源文件通过 dn 查找匹配项 用于支持 Validation
|
||||
if (!modelType.Assembly.IsDynamic && !string.IsNullOrEmpty(dn))
|
||||
{
|
||||
dn = GetLocalizerValueFromResourceManager(dn);
|
||||
}
|
||||
return dn;
|
||||
dn = stringLocalizer.Value;
|
||||
}
|
||||
else if (modelType.IsEnum)
|
||||
{
|
||||
var info = modelType.GetFieldByName(fieldName);
|
||||
if (info != null)
|
||||
{
|
||||
dn = FindDisplayAttribute(modelType, info);
|
||||
}
|
||||
}
|
||||
else if (TryGetProperty(modelType, fieldName, out var propertyInfo))
|
||||
{
|
||||
dn = FindDisplayAttribute(modelType, propertyInfo);
|
||||
}
|
||||
return dn ?? fieldName;
|
||||
}
|
||||
|
||||
private static string? FindDisplayAttribute(Type modelType, MemberInfo memberInfo)
|
||||
{
|
||||
// 回退查找 Display 标签
|
||||
var dn = memberInfo.GetCustomAttribute<DisplayAttribute>(true)?.Name
|
||||
?? memberInfo.GetCustomAttribute<DisplayNameAttribute>(true)?.DisplayName
|
||||
?? memberInfo.GetCustomAttribute<DescriptionAttribute>(true)?.Description;
|
||||
|
||||
// 回退查找资源文件通过 dn 查找匹配项 用于支持 Validation
|
||||
if (!modelType.Assembly.IsDynamic && !string.IsNullOrEmpty(dn))
|
||||
{
|
||||
dn = GetLocalizerValueFromResourceManager(dn);
|
||||
}
|
||||
return dn;
|
||||
}
|
||||
|
||||
public static List<SelectedItem> GetNullableBoolItems(Type modelType, string fieldName)
|
||||
@@ -430,83 +413,45 @@ internal class CacheManager : ICacheManager
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Range
|
||||
/// <summary>
|
||||
/// 获得类型属性的描述信息
|
||||
/// </summary>
|
||||
/// <param name="modelType"></param>
|
||||
/// <param name="fieldName"></param>
|
||||
/// <returns></returns>
|
||||
public static RangeAttribute? GetRange(Type modelType, string fieldName)
|
||||
{
|
||||
var cacheKey = $"{nameof(GetRange)}-{modelType.GetUniqueTypeName()}-{fieldName}";
|
||||
return Instance.GetOrCreate(cacheKey, entry =>
|
||||
{
|
||||
RangeAttribute? dn = null;
|
||||
if (TryGetProperty(modelType, fieldName, out var propertyInfo))
|
||||
{
|
||||
dn = propertyInfo.GetCustomAttribute<RangeAttribute>(true);
|
||||
}
|
||||
|
||||
entry.SetSlidingExpirationByType(modelType);
|
||||
return dn;
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Placeholder
|
||||
public static string? GetPlaceholder(Type modelType, string fieldName)
|
||||
{
|
||||
var cacheKey = $"{nameof(GetPlaceholder)}-{CultureInfo.CurrentUICulture.Name}-{modelType.GetUniqueTypeName()}-{fieldName}";
|
||||
return Instance.GetOrCreate(cacheKey, entry =>
|
||||
// 通过资源文件查找 FieldName 项
|
||||
string? ret = null;
|
||||
var localizer = CreateLocalizerByType(modelType);
|
||||
if (localizer != null)
|
||||
{
|
||||
// 通过资源文件查找 FieldName 项
|
||||
string? ret = null;
|
||||
var localizer = CreateLocalizerByType(modelType);
|
||||
if (localizer != null)
|
||||
var stringLocalizer = localizer[$"{fieldName}.PlaceHolder"];
|
||||
if (!stringLocalizer.ResourceNotFound)
|
||||
{
|
||||
var stringLocalizer = localizer[$"{fieldName}.PlaceHolder"];
|
||||
if (!stringLocalizer.ResourceNotFound)
|
||||
{
|
||||
ret = stringLocalizer.Value;
|
||||
}
|
||||
else if (TryGetProperty(modelType, fieldName, out var propertyInfo))
|
||||
{
|
||||
var placeHolderAttribute = propertyInfo.GetCustomAttribute<PlaceHolderAttribute>(true);
|
||||
if (placeHolderAttribute != null)
|
||||
{
|
||||
ret = placeHolderAttribute.Text;
|
||||
}
|
||||
}
|
||||
|
||||
entry.SetSlidingExpirationByType(modelType);
|
||||
ret = stringLocalizer.Value;
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
else if (TryGetProperty(modelType, fieldName, out var propertyInfo))
|
||||
{
|
||||
var placeHolderAttribute = propertyInfo.GetCustomAttribute<PlaceHolderAttribute>(true);
|
||||
if (placeHolderAttribute != null)
|
||||
{
|
||||
ret = placeHolderAttribute.Text;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Lambda Property
|
||||
public static bool TryGetProperty(Type modelType, string fieldName, [NotNullWhen(true)] out PropertyInfo? propertyInfo)
|
||||
{
|
||||
var cacheKey = $"{nameof(TryGetProperty)}-{modelType.GetUniqueTypeName()}-{fieldName}";
|
||||
propertyInfo = Instance.GetOrCreate(cacheKey, entry =>
|
||||
var props = modelType.GetRuntimeProperties();
|
||||
|
||||
// 支持 MetadataType
|
||||
var metadataType = modelType.GetCustomAttribute<MetadataTypeAttribute>(false);
|
||||
if (metadataType != null)
|
||||
{
|
||||
var props = modelType.GetRuntimeProperties().AsEnumerable();
|
||||
props = props.Concat(metadataType.MetadataClassType.GetRuntimeProperties());
|
||||
}
|
||||
|
||||
// 支持 MetadataType
|
||||
var metadataType = modelType.GetCustomAttribute<MetadataTypeAttribute>(false);
|
||||
if (metadataType != null)
|
||||
{
|
||||
props = props.Concat(metadataType.MetadataClassType.GetRuntimeProperties());
|
||||
}
|
||||
|
||||
var pi = props.FirstOrDefault(p => p.Name == fieldName);
|
||||
|
||||
entry.SetSlidingExpirationByType(modelType);
|
||||
|
||||
return pi;
|
||||
});
|
||||
propertyInfo = props.FirstOrDefault(p => p.Name == fieldName);
|
||||
return propertyInfo != null;
|
||||
}
|
||||
|
||||
@@ -525,11 +470,7 @@ internal class CacheManager : ICacheManager
|
||||
{
|
||||
var type = model.GetType();
|
||||
var cacheKey = ($"Lambda-Get-{type.GetUniqueTypeName()}", typeof(TModel), fieldName, typeof(TResult));
|
||||
var invoker = Instance.GetOrCreate(cacheKey, entry =>
|
||||
{
|
||||
entry.SetSlidingExpirationByType(type);
|
||||
return LambdaExtensions.GetPropertyValueLambda<TModel, TResult>(model, fieldName).Compile();
|
||||
})!;
|
||||
var invoker = Instance.GetOrCreate(cacheKey, entry => LambdaExtensions.GetPropertyValueLambda<TModel, TResult>(model, fieldName).Compile());
|
||||
return invoker(model);
|
||||
}
|
||||
}
|
||||
@@ -554,11 +495,7 @@ internal class CacheManager : ICacheManager
|
||||
{
|
||||
var type = model.GetType();
|
||||
var cacheKey = ($"Lambda-Set-{type.GetUniqueTypeName()}", typeof(TModel), fieldName, typeof(TValue));
|
||||
var invoker = Instance.GetOrCreate(cacheKey, entry =>
|
||||
{
|
||||
entry.SetSlidingExpirationByType(type);
|
||||
return LambdaExtensions.SetPropertyValueLambda<TModel, TValue>(model, fieldName).Compile();
|
||||
})!;
|
||||
var invoker = Instance.GetOrCreate(cacheKey, entry => LambdaExtensions.SetPropertyValueLambda<TModel, TValue>(model, fieldName).Compile());
|
||||
invoker(model, value);
|
||||
}
|
||||
}
|
||||
@@ -578,12 +515,7 @@ internal class CacheManager : ICacheManager
|
||||
{
|
||||
var type = model.GetType();
|
||||
var cacheKey = ($"Lambda-GetKeyValue-{type.GetUniqueTypeName()}-{customAttribute?.GetUniqueTypeName()}", typeof(TModel));
|
||||
var invoker = Instance.GetOrCreate(cacheKey, entry =>
|
||||
{
|
||||
entry.SetSlidingExpirationByType(type);
|
||||
|
||||
return LambdaExtensions.GetKeyValue<TModel, TValue>(customAttribute).Compile();
|
||||
})!;
|
||||
var invoker = Instance.GetOrCreate(cacheKey, entry => LambdaExtensions.GetKeyValue<TModel, TValue>(customAttribute).Compile());
|
||||
ret = invoker(model);
|
||||
}
|
||||
return ret;
|
||||
@@ -594,21 +526,13 @@ internal class CacheManager : ICacheManager
|
||||
public static Func<IEnumerable<T>, string, SortOrder, IEnumerable<T>> GetSortFunc<T>()
|
||||
{
|
||||
var cacheKey = $"Lambda-{nameof(LambdaExtensions.GetSortLambda)}-{typeof(T).GetUniqueTypeName()}";
|
||||
return Instance.GetOrCreate(cacheKey, entry =>
|
||||
{
|
||||
entry.SetSlidingExpirationByType(typeof(T));
|
||||
return LambdaExtensions.GetSortLambda<T>().Compile();
|
||||
})!;
|
||||
return Instance.GetOrCreate(cacheKey, entry => LambdaExtensions.GetSortLambda<T>().Compile());
|
||||
}
|
||||
|
||||
public static Func<IEnumerable<T>, List<string>, IEnumerable<T>> GetSortListFunc<T>()
|
||||
{
|
||||
var cacheKey = $"Lambda-{nameof(LambdaExtensions.GetSortListLambda)}-{typeof(T).GetUniqueTypeName()}";
|
||||
return Instance.GetOrCreate(cacheKey, entry =>
|
||||
{
|
||||
entry.SetSlidingExpirationByType(typeof(T));
|
||||
return LambdaExtensions.GetSortListLambda<T>().Compile();
|
||||
})!;
|
||||
return Instance.GetOrCreate(cacheKey, entry => LambdaExtensions.GetSortListLambda<T>().Compile());
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -625,10 +549,8 @@ internal class CacheManager : ICacheManager
|
||||
var para_exp = Expression.Parameter(typeof(object));
|
||||
var convert = Expression.Convert(para_exp, typeof(List<>).MakeGenericType(type));
|
||||
var body = Expression.Call(method, convert);
|
||||
|
||||
entry.SetSlidingExpirationByType(type);
|
||||
return Expression.Lambda<Func<object, IEnumerable<string?>>>(body, para_exp).Compile();
|
||||
})!;
|
||||
});
|
||||
}
|
||||
|
||||
private static IEnumerable<string?> ConvertToString<TSource>(List<TSource> source) => source is List<SelectedItem> list
|
||||
@@ -646,11 +568,7 @@ internal class CacheManager : ICacheManager
|
||||
public static Func<TModel, ITableColumn, Func<TModel, ITableColumn, object?, Task>, object> GetOnValueChangedInvoke<TModel>(Type fieldType)
|
||||
{
|
||||
var cacheKey = $"Lambda-{nameof(GetOnValueChangedInvoke)}-{typeof(TModel).GetUniqueTypeName()}-{fieldType.GetUniqueTypeName()}";
|
||||
return Instance.GetOrCreate(cacheKey, entry =>
|
||||
{
|
||||
entry.SetSlidingExpirationByType(fieldType);
|
||||
return Utility.CreateOnValueChanged<TModel>(fieldType).Compile();
|
||||
})!;
|
||||
return Instance.GetOrCreate(cacheKey, entry => Utility.CreateOnValueChanged<TModel>(fieldType).Compile());
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -658,11 +576,7 @@ internal class CacheManager : ICacheManager
|
||||
public static Func<object, string, IFormatProvider?, string> GetFormatInvoker(Type type)
|
||||
{
|
||||
var cacheKey = $"{nameof(GetFormatInvoker)}-{type.GetUniqueTypeName()}";
|
||||
return Instance.GetOrCreate(cacheKey, entry =>
|
||||
{
|
||||
entry.SetSlidingExpirationByType(type);
|
||||
return GetFormatLambda(type).Compile();
|
||||
})!;
|
||||
return Instance.GetOrCreate(cacheKey, entry => GetFormatLambda(type).Compile());
|
||||
|
||||
static Expression<Func<object, string, IFormatProvider?, string>> GetFormatLambda(Type type)
|
||||
{
|
||||
@@ -700,11 +614,7 @@ internal class CacheManager : ICacheManager
|
||||
public static Func<object, IFormatProvider?, string> GetFormatProviderInvoker(Type type)
|
||||
{
|
||||
var cacheKey = $"{nameof(GetFormatProviderInvoker)}-{type.GetUniqueTypeName()}";
|
||||
return Instance.GetOrCreate(cacheKey, entry =>
|
||||
{
|
||||
entry.SetSlidingExpirationByType(type);
|
||||
return GetFormatProviderLambda(type).Compile();
|
||||
})!;
|
||||
return Instance.GetOrCreate(cacheKey, entry => GetFormatProviderLambda(type).Compile());
|
||||
|
||||
static Expression<Func<object, IFormatProvider?, string>> GetFormatProviderLambda(Type type)
|
||||
{
|
||||
@@ -731,11 +641,7 @@ internal class CacheManager : ICacheManager
|
||||
public static object GetFormatterInvoker(Type type, Func<object, Task<string?>> formatter)
|
||||
{
|
||||
var cacheKey = $"{nameof(GetFormatterInvoker)}-{type.GetUniqueTypeName()}";
|
||||
var invoker = Instance.GetOrCreate(cacheKey, entry =>
|
||||
{
|
||||
entry.SetSlidingExpirationByType(type);
|
||||
return GetFormatterInvokerLambda(type).Compile();
|
||||
});
|
||||
var invoker = Instance.GetOrCreate(cacheKey, entry => GetFormatterInvokerLambda(type).Compile());
|
||||
return invoker(formatter);
|
||||
|
||||
static Expression<Func<Func<object, Task<string?>>, object>> GetFormatterInvokerLambda(Type type)
|
||||
@@ -748,38 +654,5 @@ internal class CacheManager : ICacheManager
|
||||
}
|
||||
|
||||
private static Func<TType, Task<string?>> InvokeFormatterAsync<TType>(Func<object?, Task<string?>> formatter) => new(v => formatter(v));
|
||||
|
||||
#endregion
|
||||
|
||||
#region TypeExtensions
|
||||
/// <summary>
|
||||
/// 通过指定类型获得所有属性信息
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static List<PropertyInfo> GetRuntimeProperties(Type type)
|
||||
{
|
||||
var cacheKey = $"{nameof(GetRuntimeProperties)}-{type.GetUniqueTypeName()}";
|
||||
return Instance.GetOrCreate(cacheKey, entry =>
|
||||
{
|
||||
entry.SetSlidingExpirationByType(type);
|
||||
return type.GetRuntimeProperties().ToList();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过指定类型获得所有字段信息
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <returns></returns>
|
||||
public static List<FieldInfo> GetRuntimeFields(Type type)
|
||||
{
|
||||
var cacheKey = $"{nameof(GetRuntimeFields)}-{type.GetUniqueTypeName()}";
|
||||
return Instance.GetOrCreate(cacheKey, entry =>
|
||||
{
|
||||
entry.SetSlidingExpirationByType(type);
|
||||
return type.GetRuntimeFields().ToList();
|
||||
})!;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -49,14 +49,6 @@ public static class Utility
|
||||
/// <returns></returns>
|
||||
public static RangeAttribute? GetRange(object model, string fieldName) => GetRange(model.GetType(), fieldName);
|
||||
|
||||
/// <summary>
|
||||
/// 获得 RangeAttribute 标签值
|
||||
/// </summary>
|
||||
/// <param name="modelType">模型类型</param>
|
||||
/// <param name="fieldName">字段名称</param>
|
||||
/// <returns></returns>
|
||||
public static RangeAttribute? GetRange(Type modelType, string fieldName) => CacheManager.GetRange(Nullable.GetUnderlyingType(modelType) ?? modelType, fieldName);
|
||||
|
||||
/// <summary>
|
||||
/// 获得 RangeAttribute 标签值
|
||||
/// </summary>
|
||||
@@ -65,6 +57,24 @@ public static class Utility
|
||||
/// <returns></returns>
|
||||
public static RangeAttribute? GetRange<TModel>(string fieldName) => GetRange(typeof(TModel), fieldName);
|
||||
|
||||
/// <summary>
|
||||
/// 获得 RangeAttribute 标签值
|
||||
/// </summary>
|
||||
/// <param name="modelType">模型类型</param>
|
||||
/// <param name="fieldName">字段名称</param>
|
||||
/// <returns></returns>
|
||||
public static RangeAttribute? GetRange(Type modelType, string fieldName)
|
||||
{
|
||||
var type = Nullable.GetUnderlyingType(modelType) ?? modelType;
|
||||
|
||||
RangeAttribute? dn = null;
|
||||
if (TryGetProperty(type, fieldName, out var propertyInfo))
|
||||
{
|
||||
dn = propertyInfo.GetCustomAttribute<RangeAttribute>(true);
|
||||
}
|
||||
return dn;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取资源文件中 NullableBoolItemsAttribute 标签名称方法
|
||||
/// </summary>
|
||||
|
||||
@@ -665,16 +665,6 @@ public class UtilityTest : BootstrapBlazorTestBase
|
||||
Assert.True(option.IgnoreLocalizerMissing);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReloadOnChange_Ok()
|
||||
{
|
||||
var option = new JsonLocalizationOptions
|
||||
{
|
||||
ReloadOnChange = true
|
||||
};
|
||||
Assert.True(option.ReloadOnChange);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetJsonStringConfig_Fallback()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user