首页 > 解决方案 > 使用 ASP.NET Core 3.1 自定义分布式缓存进行响应缓存

问题描述

我编写了一个基于 Azure BlobStorage 的自定义分布式缓存来优化页面速度。网站应该从缓存中传递缓存页面,直到缓存页面过期。这个实现应该像现有的 DistributedInMemoryCache、DistributedRedisCache 和 NCacheDistributedCache 一样工作。此处描述了操作方法https://docs.microsoft.com/de-de/aspnet/core/performance/caching/distributed?view=aspnetcore-5.0

我的问题是,在我的缓存中获取或设置的方法没有执行。

我将 IDistributedCache 实现为 DistributedBlobStorageCache 并在 ServiceCollection 扩展AddDistributedBlobStorageCache()的帮助下注册了它。所以票价这么好。

该操作具有上面的ResponseCacheAttribute,并且缓存配置文件在 Startup.cs 中设置。据我了解,系统配置是否正确,但执行分布式缓存的Get/GetAsync或Set/SetAsync方法。

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors();
        services.AddAntiforgery();
        services.AddResponseCaching();
        services.AddDistributedBlobStorageCache(options =>
        {
            options.ConnectionString = "<my connection string>";
        });
        services.AddResponseCompression();
        services.AddHttpsRedirection(options => options.RedirectStatusCode = 301);
        services.AddControllersWithViews(
            options =>
            {
                options.RespectBrowserAcceptHeader = true;

                options.CacheProfiles.Add(new KeyValuePair<string, CacheProfile>("test", new CacheProfile
                {
                    Duration = 60
                }));

                // authorization filters
                options.Filters.Add<AutoValidateAntiforgeryTokenAttribute>();
            });
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseCors();
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseResponseCaching();
        app.UseResponseCompression();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
        });
    }
}

public static class BlobStorageCacheServiceCollectionExtensions
{
    public static IServiceCollection AddDistributedBlobStorageCache(this IServiceCollection services, Action<BlobStorageCacheOptions> options)
    {
        if (options != default)
        {
            services.AddOptions();
            services.Configure(options);
        }
        return services.AddSingleton<IDistributedCache, DistributedBlobStorageCache>();
    }
}

public class BlobStorageCacheOptions : DistributedCacheEntryOptions
{
    public string ConnectionString { get; set; }
}

public class DistributedBlobStorageCache : IDistributedCache
{
    private readonly ILoggerFactory _loggerFactory;
    private readonly BlobStorageCacheOptions _options;

    public DistributedBlobStorageCache(ILoggerFactory loggerFactory, IOptions<BlobStorageCacheOptions> optionsAccessor)
    {
        _loggerFactory = loggerFactory;
        _options = optionsAccessor?.Value;
    }

    public byte[] Get(string key)
    {
        return GetAsync(key).GetAwaiter().GetResult();
    }

    public async Task<byte[]> GetAsync(string key, CancellationToken token = new CancellationToken())
    {
        var repos = CreateRepository();
        var cacheItem = await repos.GetAsync(key, token);
        if (cacheItem == null || cacheItem.ContentBytes == null)
            return Array.Empty<byte>();
        return cacheItem.ContentBytes;
    }

    public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
    {
        SetAsync(key, value, options).GetAwaiter().GetResult();
    }

    public async Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options,
        CancellationToken token = new CancellationToken())
    {
        var cacheItem = new CacheItem
        {
            ContentBytes = value,
            Key = key,
            UtcExpiry = options.AbsoluteExpiration.GetValueOrDefault(DateTimeOffset.UtcNow).DateTime
        };
        var repos = CreateRepository();
        await repos.SaveAsync(cacheItem, token);
    }

    public void Refresh(string key)
    {
        // not needed, because we use no sliding expiration
    }

    public Task RefreshAsync(string key, CancellationToken token = new CancellationToken())
    {
        // not needed, because we use no sliding expiration
        return Task.CompletedTask;
    }

    public void Remove(string key)
    {
        RemoveAsync(key).GetAwaiter().GetResult();
    }

    public async Task RemoveAsync(string key, CancellationToken token = new CancellationToken())
    {
        var repos = CreateRepository();
        await repos.RemoveAsync(key, token);
    }

    private BlobStorageCacheRepository CreateRepository()
    {
        return new BlobStorageCacheRepository(_options.ConnectionString);
    }

    private class BlobStorageCacheRepository
    {
        public BlobStorageCacheRepository(string connectionString)
        {
            
        }

        internal Task<CacheItem> GetAsync(string key, CancellationToken token)
        {
            // to implement
            return Task.FromResult(new CacheItem());
        }

        internal Task SaveAsync(CacheItem item, CancellationToken token)
        {
            // to implement
            return Task.CompletedTask;
        }

        internal Task RemoveAsync(string key, CancellationToken token)
        {
            // to implement
            return Task.CompletedTask;
        }
    }

    private class CacheItem
    {
        internal byte[] ContentBytes { get; set; }

        internal string Key { get; set; }

        internal DateTimeOffset UtcExpiry { get; set; }
    }
}   

标签: c#asp.net-coreasp.net-core-3.1distributed-cachingresponsecache

解决方案


实际上,您正在做的是两件不同的事情

关于响应缓存

services.AddResponseCaching()并且app.UseResponseCaching()用于注册ObjectPoolProviderResponseCachingMiddleware相应的,这个中间件负责缓存我们的动作端点,它曾经有过[ResponseCache]它。

这个中间件没有IDistributedCache我们上面提到的依赖。因此,两个不相关的东西,无论如何都没有任何关系。

关于IDistributedCache

这是作为准系统设计的,DistributedCache因为 .net 核心团队认为它非常适合大多数情况。IDistributedCache可以通过许多机制来实现,包括 RMDB、DocumentDb、Redis……,甚至在这种情况下,还有 blob 存储。但是,请记住,准系统对实现细节一无所知(如果我们不实现该机制,则创建一个滑动过期的条目选项什么都不做)。

例如: MemoryCache实现IMemoryCache(可以在 Microsoft.Extensions.Caching.Memory 中找到)的实例,具有Set<TItem>处理存储数据的过程的扩展,并为此设置选项条目(如过期时间),正如我们在此处看到的那样。

所以,即使我在 上看到了你的实现IDistributedCache,但我没有看到任何关于处理过期的东西,所以......那是行不通的......即使我们尝试直接使用它。

没有魔法,这就是代码的实现方式。


推荐阅读