Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions BootstrapBlazor.sln
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "cert", "cert", "{C075C6C8-B
scripts\linux\cert\www.blazor.zone.key = scripts\linux\cert\www.blazor.zone.key
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{9BAF50BE-141D-4429-93A9-942F373D1F68}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTest.Benchmarks", "tools\Benchmarks\UnitTest.Benchmarks.csproj", "{3E6D8D0E-5A36-4CFD-8612-7D64E3FFE7B1}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -104,6 +108,10 @@ Global
{D8AEAFE7-10AF-4A5B-BC67-FE740A2CA1DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D8AEAFE7-10AF-4A5B-BC67-FE740A2CA1DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D8AEAFE7-10AF-4A5B-BC67-FE740A2CA1DF}.Release|Any CPU.Build.0 = Release|Any CPU
{3E6D8D0E-5A36-4CFD-8612-7D64E3FFE7B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3E6D8D0E-5A36-4CFD-8612-7D64E3FFE7B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3E6D8D0E-5A36-4CFD-8612-7D64E3FFE7B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3E6D8D0E-5A36-4CFD-8612-7D64E3FFE7B1}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -119,6 +127,7 @@ Global
{6D73FED6-0086-460B-84FA-1FA78176BF59} = {7C1D79F1-87BC-42C1-BD5A-CDE4044AC1BD}
{D8AEAFE7-10AF-4A5B-BC67-FE740A2CA1DF} = {7C1D79F1-87BC-42C1-BD5A-CDE4044AC1BD}
{C075C6C8-B9CB-4AC0-9BDF-B2002B4AB99C} = {EA765165-0542-41C8-93F2-85787FEDEDFF}
{3E6D8D0E-5A36-4CFD-8612-7D64E3FFE7B1} = {9BAF50BE-141D-4429-93A9-942F373D1F68}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0DCB0756-34FA-4FD0-AE1D-D3F08B5B3A6B}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@ExpirationTime
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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;

namespace BootstrapBlazor.Server.Components.Pages;

/// <summary>
/// CacaheExpiration 组件
/// </summary>
public partial class CacaheExpiration
{
[Inject, NotNull]
private ICacheManager? CacheManager { get; set; }

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

private string? ExpirationTime { get; set; }

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override async Task OnParametersSetAsync()
{
await base.OnParametersSetAsync();

await GetCacheEntryExpiration();
}

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}";
}
}
}
}
5 changes: 5 additions & 0 deletions src/BootstrapBlazor.Server/Components/Pages/CacheList.razor
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
@GetValue(v.Row)
</Template>
</TableTemplateColumn>
<TableTemplateColumn Text="@Localizer["CacheListExpiration"]" Width="160">
<Template Context="v">
<CacaheExpiration Key="v.Row"></CacaheExpiration>
</Template>
</TableTemplateColumn>
<TableTemplateColumn Text="@Localizer["CacheListAction"]" Width="80">
<Template Context="v">
<Button Size="Size.ExtraSmall" Color="Color.Danger" OnClick="() => OnDelete(v.Row)" Icon="fa-solid fa-xmark" Text="@Localizer["CacheListDelete"]"></Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ private void OnRefresh()

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

private string GetValue(object key)
Expand Down
1 change: 1 addition & 0 deletions src/BootstrapBlazor.Server/Locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -6910,6 +6910,7 @@
"CacheListIntro": "Manage the component library internal cache through the <code>ICacheManager</code> interface method",
"CacheListKey": "Key",
"CacheListValue": "Value",
"CacheListExpiration": "Expiration",
"CacheListAction": "Action",
"CacheListRefresh": "Refresh",
"CacheListDelete": "Delete",
Expand Down
1 change: 1 addition & 0 deletions src/BootstrapBlazor.Server/Locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -6910,6 +6910,7 @@
"CacheListIntro": "通过 <code>ICacheManager</code> 接口方法管理组件库内部缓存",
"CacheListKey": "键",
"CacheListValue": "值",
"CacheListExpiration": "到期时间",
"CacheListAction": "操作",
"CacheListRefresh": "刷新",
"CacheListDelete": "删除",
Expand Down
48 changes: 48 additions & 0 deletions src/BootstrapBlazor/Services/CacheManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using System.Reflection;

#if NET8_0_OR_GREATER
using System.Runtime.CompilerServices;
using System.Collections.Frozen;
#endif

Expand Down Expand Up @@ -172,6 +173,53 @@ public IEnumerable<object> Keys
return keys;
}
}

private object? _coherentStateInstance = null;

private MethodInfo? _allValuesMethodInfo = null;

/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="key"></param>
/// <param name="entry"></param>
/// <returns></returns>
public bool TryGetCacheEntry(object? key, [NotNullWhen(true)] out ICacheEntry? entry)
{
entry = null;
if (key == null)
{
return false;
}

if (Cache is MemoryCache cache)
{
var values = GetAllValues(cache);
entry = values.Find(e => e.Key == key);
}
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);

var ret = new List<ICacheEntry>();
if (_allValuesMethodInfo.Invoke(_coherentStateInstance, null) is IEnumerable<ICacheEntry> values)
{
ret.AddRange(values);
}
return ret;
}
#endif

#region Count
Expand Down
8 changes: 8 additions & 0 deletions src/BootstrapBlazor/Services/ICacheManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,13 @@ public interface ICacheManager
/// 获得 缓存键集合
/// </summary>
IEnumerable<object> Keys { get; }

/// <summary>
/// 通过指定 key 获取缓存项 <see cref="ICacheEntry"/> 实例
/// </summary>
/// <param name="key"></param>
/// <param name="entry"></param>
/// <returns></returns>
bool TryGetCacheEntry(object? key, [NotNullWhen(true)] out ICacheEntry? entry);
#endif
}
41 changes: 41 additions & 0 deletions test/UnitTest/Performance/UnsafeAccessorTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// 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 System.Runtime.CompilerServices;

namespace UnitTest.Performance;

public class UnsafeAccessorTest
{
[Fact]
public void GetField_Ok()
{
var dummy = new Dummy();
dummy.SetName("test");
Assert.Equal("test", GetNameField(dummy));
}

[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_name")]
static extern ref string GetNameField(Dummy @this);

private class Dummy
{
private string? _name;

/// <summary>
///
/// </summary>
/// <returns></returns>
public string? GetName()
{
return _name;
}

public void SetName(string? name)
{
_name = name;
}
}
}
14 changes: 14 additions & 0 deletions test/UnitTest/Services/CacheManagerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,18 @@ int GetOrCreate(string key) => Cache.GetOrCreate<int>(key, entry =>
return val;
});
}

[Fact]
public void TryGetCacheEntry()
{
Cache.GetOrCreate("test_01", entry =>
{
return 1;
});
Assert.True(Cache.TryGetCacheEntry("test_01", out var entry));
Assert.NotNull(entry);

Assert.False(Cache.TryGetCacheEntry(null, out var v));
Assert.Null(v);
}
}
62 changes: 62 additions & 0 deletions tools/Benchmarks/Benmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// 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 BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Microsoft.Diagnostics.Runtime.Utilities;
using System.Dynamic;
using System.Linq.Expressions;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace UnitTest.Benchmarks;

[SimpleJob(RuntimeMoniker.Net90)]
public class Benchmarks
{
static readonly Foo _instance = new();

static readonly FieldInfo _privateField = typeof(Foo).GetField("_name", BindingFlags.Instance | BindingFlags.NonPublic)!;

[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_name")]
static extern ref string GetNameValue(Foo @this);

[Benchmark]
public object? Reflection()
{
var fieldInfo = typeof(Foo).GetField("_name", BindingFlags.Instance | BindingFlags.NonPublic)!;
return fieldInfo.GetValue(_instance);
}

[Benchmark]
public object? ReflectionWithCache() => _privateField.GetValue(_instance);

[Benchmark]
public string UnsafeAccessor() => GetNameValue(_instance);

[Benchmark]
public string DirectAccess() => _instance.GetName();

[Benchmark]
public string Lambda() => GetFieldValue();

[Benchmark]
public string LambdaWithCache() => GetFooFieldFunc(_instance);

public string GetFieldValue()
{
var method = GetFooFieldExpression().Compile();
return method.Invoke(_instance);
}

private static Func<Foo, string> GetFooFieldFunc = GetFooFieldExpression().Compile();

static Expression<Func<Foo, string>> GetFooFieldExpression()
{
var param_p1 = Expression.Parameter(typeof(Foo));
var body = Expression.Field(param_p1, _privateField);
return Expression.Lambda<Func<Foo, string>>(Expression.Convert(body, typeof(string)), param_p1);
}
}
13 changes: 13 additions & 0 deletions tools/Benchmarks/Foo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// 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

namespace UnitTest.Benchmarks;

public class Foo
{
private string _name = "test";

public string GetName() => _name;
}
11 changes: 11 additions & 0 deletions tools/Benchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// 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 BenchmarkDotNet.Running;
using UnitTest.Benchmarks;

BenchmarkRunner.Run<Benchmarks>();

Console.ReadKey();
14 changes: 14 additions & 0 deletions tools/Benchmarks/UnitTest.Benchmarks.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
</ItemGroup>

</Project>
Loading