feat(ICacheManager): add default cache policy (#5176)

* refactor: 移除扩展方法

* doc: 重构 CodeSnippetService 服务

* doc: 移除注释

* refactor: 增加默认缓存策略

* refactor: 增加 NeverRemove 约束

* refactor: 增加动态程序集默认缓存策略

* refactor: 重构代码

* test: 更新单元测试

* chore: bump version 9.2.9-beta02

* test: 更新单元测试
pack
Argo Zhang 2 days ago committed by GitHub
parent 9e809f0ba4
commit d52dda06c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -1,38 +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;
using System.Globalization;
namespace BootstrapBlazor.Server.Extensions;
/// <summary>
/// CacheManager 扩展类
/// </summary>
internal static class CacheManagerExtensions
{
/// <summary>
/// 获得 指定代码文件当前文化设置的本地化资源集合
/// </summary>
/// <param name="cache"></param>
/// <param name="typeName"></param>
/// <param name="options"></param>
/// <returns></returns>
public static IEnumerable<LocalizedString> GetLocalizedStrings(this ICacheManager cache, string typeName, JsonLocalizationOptions options)
{
var key = $"Snippet-{CultureInfo.CurrentUICulture.Name}-{nameof(GetLocalizedStrings)}-{typeName}";
return cache.GetOrCreate(key, entry =>
{
var type = typeName.Replace('\\', '.');
return Utility.GetJsonStringByTypeName(options, typeof(CodeSnippetService).Assembly, $"BootstrapBlazor.Server.Components.Samples.{type}");
});
}
public static Task<string> GetContentFromFileAsync(this ICacheManager cache, string fileName, Func<ICacheEntry, Task<string>> factory)
{
var key = $"Snippet-{CultureInfo.CurrentUICulture.Name}-{nameof(GetContentFromFileAsync)}-{fileName}";
return cache.GetOrCreateAsync(key, entry => factory(entry));
}
}

@ -4,53 +4,23 @@
// Maintainer: Argo Zhang(argo@live.ca) Website: https://www.blazor.zone
using Microsoft.Extensions.Options;
using System.Collections.Frozen;
using System.Globalization;
namespace BootstrapBlazor.Server.Services;
class CodeSnippetService
/// <summary>
/// 构造方法
/// </summary>
/// <param name="factory"></param>
/// <param name="cacheManager"></param>
/// <param name="options"></param>
/// <param name="localizerOptions"></param>
class CodeSnippetService(
IHttpClientFactory factory,
ICacheManager cacheManager,
IOptions<WebsiteOptions> options,
IOptions<JsonLocalizationOptions> localizerOptions)
{
private IHttpClientFactory Factory { get; set; }
private string ServerUrl { get; set; }
private string SourceCodePath { get; set; }
private FrozenDictionary<string, string?> SourceCodes { get; set; }
private bool IsDevelopment { get; }
private string ContentRootPath { get; }
private ICacheManager CacheManager { get; set; }
private JsonLocalizationOptions LocalizerOptions { get; }
/// <summary>
/// 构造方法
/// </summary>
/// <param name="factory"></param>
/// <param name="cacheManager"></param>
/// <param name="options"></param>
/// <param name="localizerOptions"></param>
public CodeSnippetService(
IHttpClientFactory factory,
ICacheManager cacheManager,
IOptionsMonitor<WebsiteOptions> options,
IOptionsMonitor<JsonLocalizationOptions> localizerOptions)
{
LocalizerOptions = localizerOptions.CurrentValue;
CacheManager = cacheManager;
Factory = factory;
IsDevelopment = options.CurrentValue.IsDevelopment;
ContentRootPath = options.CurrentValue.ContentRootPath;
ServerUrl = options.CurrentValue.ServerUrl;
SourceCodes = options.CurrentValue.SourceCodes;
SourceCodePath = options.CurrentValue.SourceCodePath;
}
/// <summary>
/// 获得示例源码方法
/// </summary>
@ -63,14 +33,16 @@ class CodeSnippetService
// codeFile = ajax.razor.cs
var segs = codeFile.Split('.');
var key = segs[0];
var typeName = SourceCodes.TryGetValue(key.ToLowerInvariant(), out var value) ? value : string.Empty;
var typeName = options.Value.SourceCodes.TryGetValue(key.ToLowerInvariant(), out var value) ? value : string.Empty;
if (!string.IsNullOrEmpty(typeName))
{
var fileName = codeFile.Replace(key, typeName);
content = await GetFileContentAsync(fileName);
// 源码修正
CacheManager.GetLocalizedStrings(typeName, LocalizerOptions).ToList().ForEach(l => content = ReplacePayload(content, l));
var type = typeName.Replace('\\', '.');
Utility.GetJsonStringByTypeName(localizerOptions.Value, typeof(CodeSnippetService).Assembly, $"BootstrapBlazor.Server.Components.Samples.{type}").ToList()
.ForEach(l => content = ReplacePayload(content, l));
content = ReplaceSymbols(content);
content = RemoveBlockStatement(content, "@inject IStringLocalizer<");
}
@ -93,15 +65,20 @@ class CodeSnippetService
string? payload;
if (OperatingSystem.IsBrowser())
{
var client = Factory.CreateClient();
var client = factory.CreateClient();
client.Timeout = TimeSpan.FromSeconds(5);
client.BaseAddress = new Uri($"{ServerUrl}/api/");
client.BaseAddress = new Uri($"{options.Value.ServerUrl}/api/");
payload = await client.GetStringAsync($"Code?fileName=BootstrapBlazor.Server/Components/Samples/{fileName}");
}
else
{
// 读取硬盘文件
payload = await CacheManager.GetContentFromFileAsync(fileName, _ => ReadFileAsync(fileName));
var key = $"{nameof(GetFileContentAsync)}-{fileName}-{CultureInfo.CurrentUICulture.Name}";
payload = await cacheManager.GetOrCreateAsync(key, entry =>
{
entry.SlidingExpiration = TimeSpan.FromMinutes(10);
return ReadFileAsync(fileName);
});
}
return payload;
}
@ -109,9 +86,9 @@ class CodeSnippetService
private async Task<string> ReadFileAsync(string fileName)
{
string? payload;
var file = IsDevelopment
? $"{ContentRootPath}\\..\\BootstrapBlazor.Server\\Components\\Samples\\{fileName}"
: $"{SourceCodePath}BootstrapBlazor.Server\\Components\\Samples\\{fileName}";
var file = options.Value.IsDevelopment
? $"{options.Value.ContentRootPath}\\..\\BootstrapBlazor.Server\\Components\\Samples\\{fileName}"
: $"{options.Value.SourceCodePath}BootstrapBlazor.Server\\Components\\Samples\\{fileName}";
if (!OperatingSystem.IsWindows())
{
file = file.Replace('\\', '/');

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<Version>9.2.9-beta01</Version>
<Version>9.2.9-beta02</Version>
</PropertyGroup>
<ItemGroup>

@ -36,7 +36,6 @@ internal class CacheManager : ICacheManager
/// <param name="memoryCache"></param>
public CacheManager(IServiceProvider provider, IMemoryCache memoryCache)
{
// 为了避免依赖导致的报错,构造函数避免使用其他服务
Provider = provider;
Cache = memoryCache;
Instance = this;
@ -47,11 +46,13 @@ internal class CacheManager : ICacheManager
/// </summary>
public TItem GetOrCreate<TItem>(object key, Func<ICacheEntry, TItem> factory) => Cache.GetOrCreate(key, entry =>
{
if (key is not string)
var item = factory(entry);
if (entry.SlidingExpiration == null && entry.AbsoluteExpiration == null && entry.Priority != CacheItemPriority.NeverRemove)
{
entry.SetSlidingExpiration(TimeSpan.FromMinutes(5));
}
return factory(entry);
return item;
})!;
/// <summary>
@ -90,6 +91,10 @@ internal class CacheManager : ICacheManager
/// <param name="key"></param>
public void Clear(object? key)
{
if (key is "BootstrapBlazor_StartTime")
{
return;
}
if (key is not null)
{
Cache.Remove(key);
@ -97,9 +102,6 @@ internal class CacheManager : ICacheManager
else if (Cache is MemoryCache c)
{
c.Compact(100);
var dtm = GetStartTime();
SetStartTime(dtm);
}
}
@ -113,7 +115,11 @@ internal class CacheManager : ICacheManager
/// </summary>
private void SetStartTime(DateTimeOffset startDateTimeOffset)
{
GetOrCreate("BootstrapBlazor_StartTime", _ => startDateTimeOffset);
GetOrCreate("BootstrapBlazor_StartTime", entry =>
{
entry.Priority = CacheItemPriority.NeverRemove;
return startDateTimeOffset;
});
}
/// <summary>
@ -470,7 +476,15 @@ 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 => LambdaExtensions.GetPropertyValueLambda<TModel, TResult>(model, fieldName).Compile());
var invoker = Instance.GetOrCreate(cacheKey, entry =>
{
if (type.Assembly.IsDynamic)
{
entry.SetAbsoluteExpiration(TimeSpan.FromSeconds(10));
}
return LambdaExtensions.GetPropertyValueLambda<TModel, TResult>(model, fieldName).Compile();
});
return invoker(model);
}
}
@ -487,15 +501,17 @@ internal class CacheManager : ICacheManager
d.SetValue(fieldName, value);
}
else
{
SetValue();
}
void SetValue()
{
var type = model.GetType();
var cacheKey = ($"Lambda-Set-{type.GetUniqueTypeName()}", typeof(TModel), fieldName, typeof(TValue));
var invoker = Instance.GetOrCreate(cacheKey, entry => LambdaExtensions.SetPropertyValueLambda<TModel, TValue>(model, fieldName).Compile());
var invoker = Instance.GetOrCreate(cacheKey, entry =>
{
if (type.Assembly.IsDynamic)
{
entry.SetAbsoluteExpiration(TimeSpan.FromSeconds(10));
}
return LambdaExtensions.SetPropertyValueLambda<TModel, TValue>(model, fieldName).Compile();
});
invoker(model, value);
}
}

@ -3,6 +3,8 @@
// 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 UnitTest.Services;
public class CacheManagerTest : BootstrapBlazorTestBase
@ -14,19 +16,32 @@ public class CacheManagerTest : BootstrapBlazorTestBase
var v = Cache.GetStartTime();
Assert.Equal(DateTimeOffset.MinValue, v);
Cache.GetOrCreate("BootstrapBlazor_StartTime", entry =>
{
return 10;
});
var v1 = Cache.GetStartTime();
Assert.Equal(DateTimeOffset.MinValue, v);
Cache.Clear("BootstrapBlazor_StartTime");
Cache.SetStartTime();
Assert.True(DateTime.Now > Cache.GetStartTime());
Assert.Equal(1, Cache.Count);
Cache.Clear("BootstrapBlazor_StartTime");
Assert.Equal(1, Cache.Count);
Cache.Clear();
Assert.Equal(1, Cache.Count);
Assert.Single(Cache.Keys);
Assert.NotEqual(DateTimeOffset.MinValue, Cache.GetStartTime());
}
[Fact]
public void GetStartTime_Number()
{
var context = new TestContext();
context.Services.AddBootstrapBlazor();
var cache = context.Services.GetRequiredService<ICacheManager>();
var v = cache.GetOrCreate("BootstrapBlazor_StartTime", entry =>
{
return 1;
});
Assert.Equal(1, v);
var v2 = cache.GetStartTime();
Assert.Equal(DateTimeOffset.MinValue, v2);
Assert.Empty(Cache.Keys);
}
[Fact]
@ -90,6 +105,7 @@ public class CacheManagerTest : BootstrapBlazorTestBase
int GetOrCreate(string key) => Cache.GetOrCreate<int>(key, entry =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(1);
val++;
return val;
});
@ -108,6 +124,7 @@ public class CacheManagerTest : BootstrapBlazorTestBase
int GetOrCreate(string key) => Cache.GetOrCreate<int>(key, entry =>
{
entry.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(1);
val++;
return val;
});

Loading…
Cancel
Save