diff --git a/src/BootstrapBlazor/Extensions/LocalizationOptionsExtensions.cs b/src/BootstrapBlazor/Extensions/LocalizationOptionsExtensions.cs index 95ebc85b383..55f51c52a6b 100644 --- a/src/BootstrapBlazor/Extensions/LocalizationOptionsExtensions.cs +++ b/src/BootstrapBlazor/Extensions/LocalizationOptionsExtensions.cs @@ -27,22 +27,20 @@ public static IEnumerable GetJsonStringFromAssembly(this var builder = new ConfigurationBuilder(); // 获取程序集中的资源文件 - var assemblies = new List() - { - assembly - }; + var assemblies = new List() { assembly }; var entryAssembly = GetAssembly(); if (assembly != entryAssembly) { assemblies.Add(entryAssembly); } + if (option.AdditionalJsonAssemblies != null) { assemblies.AddRange(option.AdditionalJsonAssemblies); } - var streams = assemblies.SelectMany(i => option.GetResourceStream(i, cultureName)); + var streams = assemblies.SelectMany(i => option.GetResourceStream(i, cultureName)).ToList(); // 添加 Json 文件流到配置 foreach (var s in streams) @@ -120,16 +118,17 @@ void AddStream(string name) // 开启回落机制并且当前文化信息与回落语言相同 bool EqualFallbackCulture(string name) => option.EnableFallbackCulture && option.FallbackCulture == name; + } - StringSegment GetParentCultureName(StringSegment cultureInfoName) + static StringSegment GetParentCultureName(StringSegment cultureInfoName) + { + var ret = new StringSegment(); + var index = cultureInfoName.IndexOf('-'); + if (index > 0) { - var ret = new StringSegment(); - var index = cultureInfoName.IndexOf('-'); - if (index > 0) - { - ret = cultureInfoName.Subsegment(0, index); - } - return ret; + ret = cultureInfoName.Subsegment(0, index); } + + return ret; } } diff --git a/src/BootstrapBlazor/Localization/Json/JsonStringLocalizer.cs b/src/BootstrapBlazor/Localization/Json/JsonStringLocalizer.cs index 48e8f00bd0f..de80a67cf6d 100644 --- a/src/BootstrapBlazor/Localization/Json/JsonStringLocalizer.cs +++ b/src/BootstrapBlazor/Localization/Json/JsonStringLocalizer.cs @@ -104,7 +104,8 @@ public override LocalizedString this[string name] var cacheKey = $"{nameof(GetValueFromCache)}&name={name}&{Assembly.GetUniqueName()}&type={typeName}&culture={cultureName}"; if (!CacheManager.GetMissingLocalizerByKey(cacheKey)) { - var l = GetLocalizedString(); + var l = localizerStrings?.FirstOrDefault(i => i.Name == name) + ?? CacheManager.GetAllStringsFromResolve().FirstOrDefault(i => i.Name == name); if (l is { ResourceNotFound: false }) { ret = l.Value; @@ -116,16 +117,6 @@ public override LocalizedString this[string name] } } return ret; - - LocalizedString? GetLocalizedString() - { - LocalizedString? localizer = null; - if (localizerStrings != null) - { - localizer = localizerStrings.FirstOrDefault(i => i.Name == name); - } - return localizer ?? CacheManager.GetAllStringsFromResolve().FirstOrDefault(i => i.Name == name); - } } private string? GetLocalizerValueFromCache(IStringLocalizer localizer, string name) diff --git a/src/BootstrapBlazor/Services/CacheManager.cs b/src/BootstrapBlazor/Services/CacheManager.cs index c7e626fc05a..936a8b3302f 100644 --- a/src/BootstrapBlazor/Services/CacheManager.cs +++ b/src/BootstrapBlazor/Services/CacheManager.cs @@ -12,6 +12,10 @@ using System.Linq.Expressions; using System.Reflection; +#if NET8_0_OR_GREATER +using System.Collections.Frozen; +#endif + namespace BootstrapBlazor.Components; /// @@ -118,7 +122,7 @@ public void Clear(object? key) /// private void SetStartTime(DateTimeOffset startDateTimeOffset) { - GetOrCreate("BootstrapBlazor_StartTime", entry => startDateTimeOffset); + GetOrCreate("BootstrapBlazor_StartTime", _ => startDateTimeOffset); } /// @@ -159,7 +163,7 @@ public static int ElementCount(object? value) #region Localizer /// - /// + /// 通过 Type 获得 实例 /// /// /// @@ -168,10 +172,10 @@ public static int ElementCount(object? value) : Instance.Provider.GetRequiredService().Create(resourceSource); /// - /// + /// 获得 值 /// /// - public static JsonLocalizationOptions GetJsonLocalizationOption() + private static JsonLocalizationOptions GetJsonLocalizationOption() { var localizationOptions = Instance.Provider.GetRequiredService>(); return localizationOptions.Value; @@ -185,20 +189,17 @@ public static JsonLocalizationOptions GetJsonLocalizationOption() /// public static IStringLocalizer? GetStringLocalizerFromService(Assembly assembly, string typeName) => assembly.IsDynamic ? null - : Instance.GetOrCreate($"{nameof(GetStringLocalizerFromService)}-{CultureInfo.CurrentUICulture.Name}-{assembly.GetUniqueName()}-{typeName}", entry => + : Instance.GetOrCreate($"{nameof(GetStringLocalizerFromService)}-{CultureInfo.CurrentUICulture.Name}-{assembly.GetUniqueName()}-{typeName}", _ => { IStringLocalizer? ret = null; var factories = Instance.Provider.GetServices(); - if (factories != null) + var factory = factories.LastOrDefault(a => a is not JsonStringLocalizerFactory); + if (factory != null) { - var factory = factories.LastOrDefault(a => a is not JsonStringLocalizerFactory); - if (factory != null) + var type = assembly.GetType(typeName); + if (type != null) { - var type = assembly.GetType(typeName); - if (type != null) - { - ret = factory.Create(type); - } + ret = factory.Create(type); } } return ret; @@ -209,8 +210,8 @@ public static JsonLocalizationOptions GetJsonLocalizationOption() /// /// /// - /// - public static IEnumerable? GetAllStringsByTypeName(Assembly assembly, string typeName) => GetJsonStringByTypeName(GetJsonLocalizationOption(), assembly, typeName, CultureInfo.CurrentUICulture.Name); + public static IEnumerable? GetAllStringsByTypeName(Assembly assembly, string typeName) + => GetJsonStringByTypeName(GetJsonLocalizationOption(), assembly, typeName, CultureInfo.CurrentUICulture.Name); /// /// 通过指定程序集获取所有本地化信息键值集合 @@ -223,26 +224,31 @@ public static JsonLocalizationOptions GetJsonLocalizationOption() /// public static IEnumerable? GetJsonStringByTypeName(JsonLocalizationOptions option, Assembly assembly, string typeName, string? cultureName = null, bool forceLoad = false) { - return assembly.IsDynamic ? null : GetJsonStringByTypeName(); + if (assembly.IsDynamic) + { + return null; + } - IEnumerable? GetJsonStringByTypeName() + cultureName ??= CultureInfo.CurrentUICulture.Name; + var key = $"{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{cultureName}"; + var typeKey = $"{key}-{typeName}"; + if (forceLoad) { - cultureName ??= CultureInfo.CurrentUICulture.Name; - var key = $"{nameof(GetJsonStringByTypeName)}-{assembly.GetUniqueName()}-{cultureName}"; - var typeKey = $"{key}-{typeName}"; - if (forceLoad) - { - Instance.Cache.Remove(key); - Instance.Cache.Remove(typeKey); - } - return Instance.GetOrCreate(typeKey, entry => - { - var sections = Instance.GetOrCreate(key, entry => option.GetJsonStringFromAssembly(assembly, cultureName)); - return sections.FirstOrDefault(kv => typeName.Equals(kv.Key, StringComparison.OrdinalIgnoreCase))? - .GetChildren() - .SelectMany(kv => new[] { new LocalizedString(kv.Key, kv.Value!, false, typeName) }); - }); + Instance.Cache.Remove(key); + Instance.Cache.Remove(typeKey); } + return Instance.GetOrCreate(typeKey, entry => + { + var sections = Instance.GetOrCreate(key, entry => option.GetJsonStringFromAssembly(assembly, cultureName)); + var items = sections.FirstOrDefault(kv => typeName.Equals(kv.Key, StringComparison.OrdinalIgnoreCase))? + .GetChildren() + .SelectMany(kv => new[] { new LocalizedString(kv.Key, kv.Value!, false, typeName) }); +#if NET8_0_OR_GREATER + return items?.ToFrozenSet(); +#else + return items?.ToHashSet(); +#endif + }); } /// @@ -250,7 +256,11 @@ public static JsonLocalizationOptions GetJsonLocalizationOption() /// /// /// - public static IEnumerable GetAllStringsFromResolve(bool includeParentCultures = true) => Instance.GetOrCreate($"{nameof(GetAllStringsFromResolve)}-{CultureInfo.CurrentUICulture.Name}", entry => Instance.Provider.GetRequiredService().GetAllStringsByCulture(includeParentCultures)); +#if NET8_0_OR_GREATER + public static FrozenSet GetAllStringsFromResolve(bool includeParentCultures = true) => Instance.GetOrCreate($"{nameof(GetAllStringsFromResolve)}-{CultureInfo.CurrentUICulture.Name}", entry => Instance.Provider.GetRequiredService().GetAllStringsByCulture(includeParentCultures).ToFrozenSet()); +#else + public static HashSet GetAllStringsFromResolve(bool includeParentCultures = true) => Instance.GetOrCreate($"{nameof(GetAllStringsFromResolve)}-{CultureInfo.CurrentUICulture.Name}", entry => Instance.Provider.GetRequiredService().GetAllStringsByCulture(includeParentCultures).ToHashSet()); +#endif /// /// 查询缺失本地化资源项目 diff --git a/src/BootstrapBlazor/Utils/Utility.cs b/src/BootstrapBlazor/Utils/Utility.cs index e8ff2771818..e09738f8583 100644 --- a/src/BootstrapBlazor/Utils/Utility.cs +++ b/src/BootstrapBlazor/Utils/Utility.cs @@ -156,7 +156,6 @@ public static class Utility /// 类名称 /// cultureName 未空时使用 CultureInfo.CurrentUICulture.Name /// 默认 false 使用缓存值 设置 true 时内部强制重新加载 - /// public static IEnumerable GetJsonStringByTypeName(JsonLocalizationOptions option, Assembly assembly, string typeName, string? cultureName = null, bool forceLoad = false) => CacheManager.GetJsonStringByTypeName(option, assembly, typeName, cultureName, forceLoad) ?? []; /// @@ -367,7 +366,7 @@ public static IEnumerable GetTableColumns(Type type, IEnumerable OrderFunc(this IEnumerable cols) => cols + internal static IEnumerable OrderFunc(this List cols) => cols .Where(a => a.Order > 0).OrderBy(a => a.Order) .Concat(cols.Where(a => a.Order == 0)) .Concat(cols.Where(a => a.Order < 0).OrderBy(a => a.Order)); @@ -404,7 +403,6 @@ public static void CreateDisplayByFieldType(this RenderTreeBuilder builder, IEdi builder.AddAttribute(50, "class", col.CssClass); } builder.AddMultipleAttributes(60, item.ComponentParameters); - builder.CloseComponent(); } else if (item.ComponentType == typeof(Textarea) || item.Rows > 0) { @@ -422,7 +420,6 @@ public static void CreateDisplayByFieldType(this RenderTreeBuilder builder, IEdi builder.AddAttribute(60, "class", col.CssClass); } builder.AddMultipleAttributes(70, item.ComponentParameters); - builder.CloseComponent(); } else { @@ -448,8 +445,9 @@ public static void CreateDisplayByFieldType(this RenderTreeBuilder builder, IEdi builder.AddAttribute(90, "class", col.CssClass); } builder.AddMultipleAttributes(100, item.ComponentParameters); - builder.CloseComponent(); } + + builder.CloseComponent(); } /// diff --git a/test/UnitTest/Performance/CacheTest.cs b/test/UnitTest/Performance/CacheTest.cs index 8915958852c..57bd2383dfd 100644 --- a/test/UnitTest/Performance/CacheTest.cs +++ b/test/UnitTest/Performance/CacheTest.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Localization; using System.Collections.Concurrent; +using System.Collections.Frozen; using System.Diagnostics; namespace UnitTest.Performance; @@ -53,6 +54,35 @@ public void Cache_Ok() IEnumerable CacheMethod() => cache.GetOrAdd("test", key => NoCacheMethod()); } + [Fact] + public void List_Perf() + { + var listItms = GetListLocalizedStrings(); + var setItems = GetSetLocalizedStrings(); + var frozenItems = GetFrozenLocalizedStrings(); + + var sw = Stopwatch.StartNew(); + listItms.FirstOrDefault(i => i.Name == "500000"); + sw.Stop(); + var sp1 = sw.Elapsed; + + sw.Restart(); + setItems.FirstOrDefault(i => i.Name == "500000"); + sw.Stop(); + var sp2 = sw.Elapsed; + + sw.Restart(); + frozenItems.FirstOrDefault(i => i.Name == "500000"); + sw.Stop(); + var sp3 = sw.Elapsed; + } + + private IEnumerable GetListLocalizedStrings() => Enumerable.Range(1, 1000000).Select(i => new LocalizedString($"{i}", $"{i}", false, nameof(CacheTest))); + + private IEnumerable GetSetLocalizedStrings() => Enumerable.Range(1, 1000000).Select(i => new LocalizedString($"{i}", $"{i}", false, nameof(CacheTest))).ToHashSet(); + + private IEnumerable GetFrozenLocalizedStrings() => Enumerable.Range(1, 1000000).Select(i => new LocalizedString($"{i}", $"{i}", false, nameof(CacheTest))).ToFrozenSet(); + private IEnumerable NoCacheMethod() => Enumerable.Range(1, 80).Select(i => new Foo() { Id = i,