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:
Argo Zhang
2025-01-20 10:22:20 +08:00
committed by GitHub
parent 72868a66e6
commit da7715efb8
14 changed files with 267 additions and 303 deletions

View 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>

View File

@@ -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;
}
}

View File

@@ -0,0 +1,3 @@
.table-cache-list {
height: calc(100vh - 180px);
}

View File

@@ -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>

View File

@@ -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"
}
}

View File

@@ -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} 个键值"
}
}

View File

@@ -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;
}
}

View File

@@ -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)

View File

@@ -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.");

View File

@@ -51,6 +51,8 @@ public class JsonLocalizationOptions : LocalizationOptions
/// <summary>
/// 获得/设置 资源文件是否热加载 默认 false
/// </summary>
[Obsolete("已弃用 Deprecated")]
[ExcludeFromCodeCoverage]
public bool ReloadOnChange { get; set; }
/// <summary>

View File

@@ -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)
{

View File

@@ -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
}

View File

@@ -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>

View File

@@ -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()
{