Skip to content

Commit

Permalink
feat(ICacheEntry): add GetLastAccessed extension method (#5219)
Browse files Browse the repository at this point in the history
* doc: 更改示例代码

* doc: 增加缓存时长描述

* refactor: 更改 OnGetDisplayText 增加可为空

* refactor: 重构 CacheManager 优化性能

* doc: 更新示例

* refactor: 增加扩展方法

* doc: 更新超时时长列内容

* doc: 实现过期时间实时更新

* refactor: 精简代码

* test: 更新单元测试

* test: 更新单元测试
  • Loading branch information
ArgoZhang authored Jan 26, 2025
1 parent f0fd5dc commit 0d04f6f
Show file tree
Hide file tree
Showing 11 changed files with 195 additions and 64 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,11 @@ namespace BootstrapBlazor.Server.Components.Pages;
/// </summary>
public partial class CacaheExpiration
{
[Inject, NotNull]
private ICacheManager? CacheManager { get; set; }

/// <summary>
/// 获得/设置 <see cref="TableColumnContext{TItem, TValue}"/> 实例
/// 获得/设置 <see cref="ICacheEntry"/> 实例
/// </summary>
[Parameter, NotNull]
public object? Key { get; set; }
public object? Context { get; set; }

private string? ExpirationTime { get; set; }

Expand All @@ -39,24 +36,6 @@ private async Task GetCacheEntryExpiration()
ExpirationTime = "loading ...";
await Task.Yield();

if (CacheManager.TryGetCacheEntry(Key, out ICacheEntry? entry))
{
if (entry.Priority == CacheItemPriority.NeverRemove)
{
ExpirationTime = "Never Remove";
}
else if (entry.SlidingExpiration.HasValue)
{
ExpirationTime = $"Sliding: {entry.SlidingExpiration.Value}";
}
else if (entry.AbsoluteExpiration.HasValue)
{
ExpirationTime = $"Absolute: {entry.AbsoluteExpiration.Value}";
}
else if (entry.ExpirationTokens.Count != 0)
{
ExpirationTime = $"Token: {entry.ExpirationTokens.Count}";
}
}
ExpirationTime = Context is ICacheEntry entry ? entry.GetExpiration() : "-";
}
}
6 changes: 3 additions & 3 deletions src/BootstrapBlazor.Server/Components/Pages/CacheList.razor
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,17 @@
<TableColumns>
<TableTemplateColumn Text="@Localizer["CacheListKey"]" TextWrap="true">
<Template Context="v">
@v.Row.ToString()
@GetKey(v.Row)
</Template>
</TableTemplateColumn>
<TableTemplateColumn Text="@Localizer["CacheListValue"]" TextWrap="true" Width="220">
<Template Context="v">
@GetValue(v.Row)
</Template>
</TableTemplateColumn>
<TableTemplateColumn Text="@Localizer["CacheListExpiration"]" Width="160">
<TableTemplateColumn Text="@Localizer["CacheListExpiration"]" Width="180">
<Template Context="v">
<CacaheExpiration Key="v.Row"></CacaheExpiration>
<CacaheExpiration Context="v.Row"></CacaheExpiration>
</Template>
</TableTemplateColumn>
<TableTemplateColumn Text="@Localizer["CacheListAction"]" Width="80">
Expand Down
43 changes: 22 additions & 21 deletions src/BootstrapBlazor.Server/Components/Pages/CacheList.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ public partial class CacheList
/// <summary>
/// <inheritdoc/>
/// </summary>
protected override void OnInitialized()
protected override async Task OnParametersSetAsync()
{
base.OnInitialized();
await base.OnParametersSetAsync();

await Task.Yield();
UpdateCacheList();
}

Expand All @@ -49,30 +51,29 @@ private void OnRefresh()

private void UpdateCacheList()
{
_cacheList = [.. CacheManager.Keys.OrderBy(i => i.ToString())];
}

private string GetValue(object key)
{
string ret = "-";
if (CacheManager.TryGetValue(key, out object? value))
_cacheList = CacheManager.Keys.OrderBy(i => i.ToString()).Select(key =>
{
if (value is string stringValue)
ICacheEntry? entry = null;
if (CacheManager.TryGetCacheEntry(key, out var val))
{
ret = stringValue;
return ret;
entry = val;
}
return (object)entry!;
}).ToList();
}

if (value is IEnumerable)
{
ret = $"{LambdaExtensions.ElementCount(value)}";
}
else
{
ret = value?.ToString() ?? "-";
}
private static string GetKey(object data) => data is ICacheEntry entry ? entry.Key.ToString()! : "-";

private static string GetValue(object data) => data is ICacheEntry entry ? GetCacheEntryValue(entry) : "-";

private static string GetCacheEntryValue(ICacheEntry entry)
{
var value = entry.Value;
if (value is string stringValue)
{
return stringValue;
}

return ret;
return value is IEnumerable ? $"{LambdaExtensions.ElementCount(value)}" : value?.ToString() ?? "-";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ partial class AutoFills
[NotNull]
private Foo Model3 { get; set; } = new();

private static string OnGetDisplayText(Foo foo) => foo.Name ?? "";
private static string? OnGetDisplayText(Foo? foo) => foo?.Name;

[NotNull]
private IEnumerable<Foo>? Items1 { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
<div class="col-12 col-sm-6">
<BootstrapInputGroup>
<BootstrapInputGroupLabel DisplayText="AutoFill" />
<AutoFill Items="AufoFillItems" IsLikeMatch="true" OnGetDisplayText="@(foo => foo.Name)">
<AutoFill Items="AufoFillItems" IsLikeMatch="true" OnGetDisplayText="@(foo => foo?.Name)">
<ItemTemplate>
<div class="d-flex">
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ protected override void OnInitialized()

private readonly IEnumerable<SelectedItem> Items = new SelectedItem[]
{
new SelectedItem("", "请选择 ..."),
new SelectedItem("Beijing", "北京"),
new SelectedItem("Shanghai", "上海")
new("", "请选择 ..."),
new("Beijing", "北京"),
new("Shanghai", "上海")
};
private string? GroupFormClassString => CssBuilder.Default("row g-3").AddClass("form-inline", FormRowType == RowType.Inline).Build();

Expand Down
52 changes: 52 additions & 0 deletions src/BootstrapBlazor.Server/Extensions/ICacheEntryExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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([email protected]) Website: https://www.blazor.zone

using Microsoft.Extensions.Caching.Memory;
using System.Reflection;

namespace BootstrapBlazor.Server.Extensions;

/// <summary>
/// <see cref="ICacheEntry"/> 扩展方法
/// </summary>
public static class ICacheEntryExtensions
{
/// <summary>
/// 获取缓存项过期时间
/// </summary>
/// <param name="entry"></param>
/// <returns></returns>
public static string GetExpiration(this ICacheEntry entry)
{
string? ret;
if (entry.Priority == CacheItemPriority.NeverRemove)
{
ret = "Never Remove";
}
else if (entry.SlidingExpiration.HasValue)
{
ret = $"Sliding: {entry.GetSlidingLeftTime().TotalSeconds:###}/{entry.SlidingExpiration.Value.TotalSeconds}";
}
else if (entry.AbsoluteExpiration.HasValue)
{
ret = $"Absolute: {entry.AbsoluteExpiration.Value}";
}
else if (entry.ExpirationTokens.Count != 0)
{
ret = $"Token: {entry.ExpirationTokens.Count}";
}
else
{
ret = "Not Set";
}
return ret;
}

private static TimeSpan GetSlidingLeftTime(this ICacheEntry entry)
{
var lastAccessed = entry.GetLastAccessed();
return lastAccessed == null ? TimeSpan.Zero : entry.SlidingExpiration!.Value - (DateTime.UtcNow - lastAccessed.Value);
}
}
2 changes: 1 addition & 1 deletion src/BootstrapBlazor/Components/AutoFill/AutoFill.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public partial class AutoFill<TValue>
/// </summary>
[Parameter]
[NotNull]
public Func<TValue, string?>? OnGetDisplayText { get; set; }
public Func<TValue?, string?>? OnGetDisplayText { get; set; }

/// <summary>
/// 图标
Expand Down
43 changes: 43 additions & 0 deletions src/BootstrapBlazor/Extensions/ICacheEntryExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// 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([email protected]) Website: https://www.blazor.zone

using Microsoft.Extensions.Caching.Memory;
using System.Reflection;

namespace BootstrapBlazor.Components;

/// <summary>
/// <see cref="ICacheEntry"/> 扩展方法
/// </summary>
public static class ICacheEntryExtensions
{
/// <summary>
/// 获得缓存项 <see cref="ICacheEntry"/> 最后访问时间
/// </summary>
/// <param name="entry"></param>
/// <param name="force"></param>
/// <returns></returns>
public static DateTime? GetLastAccessed(this ICacheEntry entry, bool force = false)
{
if (force)
{
_lastAccessedProperty = null;
}
_lastAccessedProperty ??= entry.GetType().GetProperty("LastAccessed", BindingFlags.Instance | BindingFlags.NonPublic);

DateTime? ret = null;
if (_lastAccessedProperty != null)
{
var v = _lastAccessedProperty.GetValue(entry);
if (v is DateTime val)
{
ret = val;
}
}
return ret;
}

private static PropertyInfo? _lastAccessedProperty = null;
}
16 changes: 6 additions & 10 deletions src/BootstrapBlazor/Services/CacheManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ public IEnumerable<object> Keys

private MethodInfo? _allValuesMethodInfo = null;

private static readonly FieldInfo _coherentStateFieldInfo = typeof(MemoryCache).GetField("_coherentState", BindingFlags.Instance | BindingFlags.NonPublic)!;

private static MethodInfo GetAllValuesMethodInfo(Type type) => type.GetMethod("GetAllValues", BindingFlags.Instance | BindingFlags.Public)!;

/// <summary>
/// <inheritdoc/>
/// </summary>
Expand All @@ -200,18 +204,10 @@ public bool TryGetCacheEntry(object? key, [NotNullWhen(true)] out ICacheEntry? e
return entry != null;
}

private static object GetCoherentState(MemoryCache cache)
{
var fieldInfo = cache.GetType().GetField("_coherentState", BindingFlags.Instance | BindingFlags.NonPublic)!;
return fieldInfo.GetValue(cache)!;
}

private static MethodInfo GetAllValuesMethodInfo(object coherentStateInstance) => coherentStateInstance.GetType().GetMethod("GetAllValues", BindingFlags.Instance | BindingFlags.Public)!;

private List<ICacheEntry> GetAllValues(MemoryCache cache)
{
_coherentStateInstance ??= GetCoherentState(cache);
_allValuesMethodInfo ??= GetAllValuesMethodInfo(_coherentStateInstance);
_coherentStateInstance = _coherentStateFieldInfo.GetValue(cache)!;
_allValuesMethodInfo ??= GetAllValuesMethodInfo(_coherentStateInstance.GetType());

var ret = new List<ICacheEntry>();
if (_allValuesMethodInfo.Invoke(_coherentStateInstance, null) is IEnumerable<ICacheEntry> values)
Expand Down
60 changes: 60 additions & 0 deletions test/UnitTest/Extensions/ICacheEntryExtensionsTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// 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([email protected]) Website: https://www.blazor.zone

using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Primitives;

namespace UnitTest.Extensions;

public class ICacheEntryExtensionsTest : BootstrapBlazorTestBase
{
[Fact]
public void GetLastAccessed_Ok()
{
Cache.GetOrCreate("test_01", entry =>
{
return 1;
});

Assert.True(Cache.TryGetCacheEntry("test_01", out var entry));
var v = entry.GetLastAccessed(true);
Assert.NotNull(v);
}

[Fact]
public void GetLastAccessed_Null()
{
var mock = new MockCacheEntry();
var v = mock.GetLastAccessed(true);
Assert.Null(v);
}

class MockCacheEntry : ICacheEntry
{
public object Key { get; }
public object? Value { get; set; }
public DateTimeOffset? AbsoluteExpiration { get; set; }
public TimeSpan? AbsoluteExpirationRelativeToNow { get; set; }
public TimeSpan? SlidingExpiration { get; set; }
public IList<IChangeToken> ExpirationTokens { get; }
public IList<PostEvictionCallbackRegistration> PostEvictionCallbacks { get; }
public CacheItemPriority Priority { get; set; }
public long? Size { get; set; }

private int LastAccessed { get; set; }

public MockCacheEntry()
{
Key = "_test";
ExpirationTokens = [];
PostEvictionCallbacks = [];
}

public void Dispose()
{
throw new NotImplementedException();
}
}
}

0 comments on commit 0d04f6f

Please sign in to comment.