| File: CacheView\HybridCacheViewStore.cs | Web Access |
| Project: src\aspnetcore\src\Components\Endpoints\src\Microsoft.AspNetCore.Components.Endpoints.csproj (Microsoft.AspNetCore.Components.Endpoints) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Extensions.Caching.Hybrid; namespace Microsoft.AspNetCore.Components.Endpoints; internal sealed class HybridCacheViewStore : ICacheViewStore { private const string CacheViewTag = "Microsoft.AspNetCore.Components.Endpoints.CacheView"; private static readonly string[] _tags = [CacheViewTag]; private readonly HybridCache _hybridCache; public HybridCacheViewStore(HybridCache hybridCache) { _hybridCache = hybridCache; } public ValueTask<SerializedRenderFragment> GetOrCreateAsync( string key, Func<CancellationToken, ValueTask<SerializedRenderFragment>> factory, CacheStoreOptions options, CancellationToken cancellationToken) { var hybridOptions = BuildHybridOptions(options); // HybridCache provides single-flight stampede protection: concurrent callers for one key coalesce // onto a single factory invocation, and the shared factory is only cancelled when the last enlisted // caller cancels (ref-counted). No re-election is needed here. return _hybridCache.GetOrCreateAsync(key, factory, static (state, ct) => state(ct), hybridOptions, _tags, cancellationToken); } public void Clear() { // Eviction is used only for hot reload, so it is best-effort; fire-and-forget to avoid blocking // a thread on the async call. _ = ClearCoreAsync(); } private async Task ClearCoreAsync() { try { await _hybridCache.RemoveByTagAsync(CacheViewTag); } catch { // Best-effort cache eviction; ignore failures. } } private static HybridCacheEntryOptions BuildHybridOptions(CacheStoreOptions options) { if (options.ExpiresSliding.HasValue) { throw new NotSupportedException( $"{nameof(CacheView)}.{nameof(CacheView.ExpiresSliding)} is not supported when the cache view store uses HybridCache. " + $"Use {nameof(CacheView.ExpiresAfter)} or {nameof(CacheView.ExpiresOn)} for absolute expiration."); } var absolute = options.ExpiresOn.HasValue ? options.ExpiresOn.Value - DateTimeOffset.UtcNow : options.ExpiresAfter ?? RazorComponentsServiceOptions.DefaultCacheViewExpiration; if (absolute < TimeSpan.Zero) { absolute = TimeSpan.Zero; } return new HybridCacheEntryOptions { Expiration = absolute, }; } public void Dispose() { } }