Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ICacheEntry): add GetLastAccessed extension method #5219

Merged
merged 11 commits into from
Jan 26, 2025
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();
ArgoZhang marked this conversation as resolved.
Show resolved Hide resolved
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);
ArgoZhang marked this conversation as resolved.
Show resolved Hide resolved
}
}
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()
ArgoZhang marked this conversation as resolved.
Show resolved Hide resolved
{
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();
}
}
}
Loading