Skip to content

Commit d52dda0

Browse files
authored
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: 更新单元测试
1 parent 9e809f0 commit d52dda0

File tree

5 files changed

+86
-114
lines changed

5 files changed

+86
-114
lines changed

src/BootstrapBlazor.Server/Extensions/CacheManagerExtensions.cs

Lines changed: 0 additions & 38 deletions
This file was deleted.

src/BootstrapBlazor.Server/Services/CodeSnippetService.cs

Lines changed: 28 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -4,53 +4,23 @@
44
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
55

66
using Microsoft.Extensions.Options;
7-
using System.Collections.Frozen;
7+
using System.Globalization;
88

99
namespace BootstrapBlazor.Server.Services;
1010

11-
class CodeSnippetService
11+
/// <summary>
12+
/// 构造方法
13+
/// </summary>
14+
/// <param name="factory"></param>
15+
/// <param name="cacheManager"></param>
16+
/// <param name="options"></param>
17+
/// <param name="localizerOptions"></param>
18+
class CodeSnippetService(
19+
IHttpClientFactory factory,
20+
ICacheManager cacheManager,
21+
IOptions<WebsiteOptions> options,
22+
IOptions<JsonLocalizationOptions> localizerOptions)
1223
{
13-
private IHttpClientFactory Factory { get; set; }
14-
15-
private string ServerUrl { get; set; }
16-
17-
private string SourceCodePath { get; set; }
18-
19-
private FrozenDictionary<string, string?> SourceCodes { get; set; }
20-
21-
private bool IsDevelopment { get; }
22-
23-
private string ContentRootPath { get; }
24-
25-
private ICacheManager CacheManager { get; set; }
26-
27-
private JsonLocalizationOptions LocalizerOptions { get; }
28-
29-
/// <summary>
30-
/// 构造方法
31-
/// </summary>
32-
/// <param name="factory"></param>
33-
/// <param name="cacheManager"></param>
34-
/// <param name="options"></param>
35-
/// <param name="localizerOptions"></param>
36-
public CodeSnippetService(
37-
IHttpClientFactory factory,
38-
ICacheManager cacheManager,
39-
IOptionsMonitor<WebsiteOptions> options,
40-
IOptionsMonitor<JsonLocalizationOptions> localizerOptions)
41-
{
42-
LocalizerOptions = localizerOptions.CurrentValue;
43-
44-
CacheManager = cacheManager;
45-
Factory = factory;
46-
47-
IsDevelopment = options.CurrentValue.IsDevelopment;
48-
ContentRootPath = options.CurrentValue.ContentRootPath;
49-
ServerUrl = options.CurrentValue.ServerUrl;
50-
SourceCodes = options.CurrentValue.SourceCodes;
51-
SourceCodePath = options.CurrentValue.SourceCodePath;
52-
}
53-
5424
/// <summary>
5525
/// 获得示例源码方法
5626
/// </summary>
@@ -63,14 +33,16 @@ public async Task<string> GetCodeAsync(string codeFile)
6333
// codeFile = ajax.razor.cs
6434
var segs = codeFile.Split('.');
6535
var key = segs[0];
66-
var typeName = SourceCodes.TryGetValue(key.ToLowerInvariant(), out var value) ? value : string.Empty;
36+
var typeName = options.Value.SourceCodes.TryGetValue(key.ToLowerInvariant(), out var value) ? value : string.Empty;
6737
if (!string.IsNullOrEmpty(typeName))
6838
{
6939
var fileName = codeFile.Replace(key, typeName);
7040
content = await GetFileContentAsync(fileName);
7141

7242
// 源码修正
73-
CacheManager.GetLocalizedStrings(typeName, LocalizerOptions).ToList().ForEach(l => content = ReplacePayload(content, l));
43+
var type = typeName.Replace('\\', '.');
44+
Utility.GetJsonStringByTypeName(localizerOptions.Value, typeof(CodeSnippetService).Assembly, $"BootstrapBlazor.Server.Components.Samples.{type}").ToList()
45+
.ForEach(l => content = ReplacePayload(content, l));
7446
content = ReplaceSymbols(content);
7547
content = RemoveBlockStatement(content, "@inject IStringLocalizer<");
7648
}
@@ -93,25 +65,30 @@ public async Task<string> GetFileContentAsync(string fileName)
9365
string? payload;
9466
if (OperatingSystem.IsBrowser())
9567
{
96-
var client = Factory.CreateClient();
68+
var client = factory.CreateClient();
9769
client.Timeout = TimeSpan.FromSeconds(5);
98-
client.BaseAddress = new Uri($"{ServerUrl}/api/");
70+
client.BaseAddress = new Uri($"{options.Value.ServerUrl}/api/");
9971
payload = await client.GetStringAsync($"Code?fileName=BootstrapBlazor.Server/Components/Samples/{fileName}");
10072
}
10173
else
10274
{
10375
// 读取硬盘文件
104-
payload = await CacheManager.GetContentFromFileAsync(fileName, _ => ReadFileAsync(fileName));
76+
var key = $"{nameof(GetFileContentAsync)}-{fileName}-{CultureInfo.CurrentUICulture.Name}";
77+
payload = await cacheManager.GetOrCreateAsync(key, entry =>
78+
{
79+
entry.SlidingExpiration = TimeSpan.FromMinutes(10);
80+
return ReadFileAsync(fileName);
81+
});
10582
}
10683
return payload;
10784
}
10885

10986
private async Task<string> ReadFileAsync(string fileName)
11087
{
11188
string? payload;
112-
var file = IsDevelopment
113-
? $"{ContentRootPath}\\..\\BootstrapBlazor.Server\\Components\\Samples\\{fileName}"
114-
: $"{SourceCodePath}BootstrapBlazor.Server\\Components\\Samples\\{fileName}";
89+
var file = options.Value.IsDevelopment
90+
? $"{options.Value.ContentRootPath}\\..\\BootstrapBlazor.Server\\Components\\Samples\\{fileName}"
91+
: $"{options.Value.SourceCodePath}BootstrapBlazor.Server\\Components\\Samples\\{fileName}";
11592
if (!OperatingSystem.IsWindows())
11693
{
11794
file = file.Replace('\\', '/');

src/BootstrapBlazor/BootstrapBlazor.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk.Razor">
22

33
<PropertyGroup>
4-
<Version>9.2.9-beta01</Version>
4+
<Version>9.2.9-beta02</Version>
55
</PropertyGroup>
66

77
<ItemGroup>

src/BootstrapBlazor/Services/CacheManager.cs

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ internal class CacheManager : ICacheManager
3636
/// <param name="memoryCache"></param>
3737
public CacheManager(IServiceProvider provider, IMemoryCache memoryCache)
3838
{
39-
// 为了避免依赖导致的报错,构造函数避免使用其他服务
4039
Provider = provider;
4140
Cache = memoryCache;
4241
Instance = this;
@@ -47,11 +46,13 @@ public CacheManager(IServiceProvider provider, IMemoryCache memoryCache)
4746
/// </summary>
4847
public TItem GetOrCreate<TItem>(object key, Func<ICacheEntry, TItem> factory) => Cache.GetOrCreate(key, entry =>
4948
{
50-
if (key is not string)
49+
var item = factory(entry);
50+
51+
if (entry.SlidingExpiration == null && entry.AbsoluteExpiration == null && entry.Priority != CacheItemPriority.NeverRemove)
5152
{
5253
entry.SetSlidingExpiration(TimeSpan.FromMinutes(5));
5354
}
54-
return factory(entry);
55+
return item;
5556
})!;
5657

5758
/// <summary>
@@ -90,16 +91,17 @@ public bool TryGetValue<TItem>(object key, [NotNullWhen(true)] out TItem? value)
9091
/// <param name="key"></param>
9192
public void Clear(object? key)
9293
{
94+
if (key is "BootstrapBlazor_StartTime")
95+
{
96+
return;
97+
}
9398
if (key is not null)
9499
{
95100
Cache.Remove(key);
96101
}
97102
else if (Cache is MemoryCache c)
98103
{
99104
c.Compact(100);
100-
101-
var dtm = GetStartTime();
102-
SetStartTime(dtm);
103105
}
104106
}
105107

@@ -113,7 +115,11 @@ public void Clear(object? key)
113115
/// </summary>
114116
private void SetStartTime(DateTimeOffset startDateTimeOffset)
115117
{
116-
GetOrCreate("BootstrapBlazor_StartTime", _ => startDateTimeOffset);
118+
GetOrCreate("BootstrapBlazor_StartTime", entry =>
119+
{
120+
entry.Priority = CacheItemPriority.NeverRemove;
121+
return startDateTimeOffset;
122+
});
117123
}
118124

119125
/// <summary>
@@ -470,7 +476,15 @@ TResult GetValue()
470476
{
471477
var type = model.GetType();
472478
var cacheKey = ($"Lambda-Get-{type.GetUniqueTypeName()}", typeof(TModel), fieldName, typeof(TResult));
473-
var invoker = Instance.GetOrCreate(cacheKey, entry => LambdaExtensions.GetPropertyValueLambda<TModel, TResult>(model, fieldName).Compile());
479+
var invoker = Instance.GetOrCreate(cacheKey, entry =>
480+
{
481+
if (type.Assembly.IsDynamic)
482+
{
483+
entry.SetAbsoluteExpiration(TimeSpan.FromSeconds(10));
484+
}
485+
486+
return LambdaExtensions.GetPropertyValueLambda<TModel, TResult>(model, fieldName).Compile();
487+
});
474488
return invoker(model);
475489
}
476490
}
@@ -487,15 +501,17 @@ public static void SetPropertyValue<TModel, TValue>(TModel model, string fieldNa
487501
d.SetValue(fieldName, value);
488502
}
489503
else
490-
{
491-
SetValue();
492-
}
493-
494-
void SetValue()
495504
{
496505
var type = model.GetType();
497506
var cacheKey = ($"Lambda-Set-{type.GetUniqueTypeName()}", typeof(TModel), fieldName, typeof(TValue));
498-
var invoker = Instance.GetOrCreate(cacheKey, entry => LambdaExtensions.SetPropertyValueLambda<TModel, TValue>(model, fieldName).Compile());
507+
var invoker = Instance.GetOrCreate(cacheKey, entry =>
508+
{
509+
if (type.Assembly.IsDynamic)
510+
{
511+
entry.SetAbsoluteExpiration(TimeSpan.FromSeconds(10));
512+
}
513+
return LambdaExtensions.SetPropertyValueLambda<TModel, TValue>(model, fieldName).Compile();
514+
});
499515
invoker(model, value);
500516
}
501517
}

test/UnitTest/Services/CacheManagerTest.cs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
// See the LICENSE file in the project root for more information.
44
// Maintainer: Argo Zhang([email protected]) Website: https://www.blazor.zone
55

6+
using Microsoft.Extensions.Caching.Memory;
7+
68
namespace UnitTest.Services;
79

810
public class CacheManagerTest : BootstrapBlazorTestBase
@@ -14,19 +16,32 @@ public void GetStartTime_Ok()
1416
var v = Cache.GetStartTime();
1517
Assert.Equal(DateTimeOffset.MinValue, v);
1618

17-
Cache.GetOrCreate("BootstrapBlazor_StartTime", entry =>
18-
{
19-
return 10;
20-
});
21-
var v1 = Cache.GetStartTime();
22-
Assert.Equal(DateTimeOffset.MinValue, v);
23-
24-
Cache.Clear("BootstrapBlazor_StartTime");
2519
Cache.SetStartTime();
26-
Assert.True(DateTime.Now > Cache.GetStartTime());
20+
Assert.Equal(1, Cache.Count);
21+
Cache.Clear("BootstrapBlazor_StartTime");
22+
Assert.Equal(1, Cache.Count);
2723

24+
Cache.Clear();
2825
Assert.Equal(1, Cache.Count);
29-
Assert.Single(Cache.Keys);
26+
Assert.NotEqual(DateTimeOffset.MinValue, Cache.GetStartTime());
27+
}
28+
29+
[Fact]
30+
public void GetStartTime_Number()
31+
{
32+
var context = new TestContext();
33+
context.Services.AddBootstrapBlazor();
34+
var cache = context.Services.GetRequiredService<ICacheManager>();
35+
36+
var v = cache.GetOrCreate("BootstrapBlazor_StartTime", entry =>
37+
{
38+
return 1;
39+
});
40+
Assert.Equal(1, v);
41+
42+
var v2 = cache.GetStartTime();
43+
Assert.Equal(DateTimeOffset.MinValue, v2);
44+
Assert.Empty(Cache.Keys);
3045
}
3146

3247
[Fact]
@@ -90,6 +105,7 @@ public void Clear_Ok()
90105

91106
int GetOrCreate(string key) => Cache.GetOrCreate<int>(key, entry =>
92107
{
108+
entry.SlidingExpiration = TimeSpan.FromSeconds(1);
93109
val++;
94110
return val;
95111
});
@@ -108,6 +124,7 @@ public void MemoryCacheClear_Ok()
108124

109125
int GetOrCreate(string key) => Cache.GetOrCreate<int>(key, entry =>
110126
{
127+
entry.AbsoluteExpiration = DateTimeOffset.Now.AddSeconds(1);
111128
val++;
112129
return val;
113130
});

0 commit comments

Comments
 (0)