使用 ASP.NET Core 3.1 自定义分布式缓存进行响应缓存
Using ASP.NET Core 3.1 custom distributed cache for respones caching
我编写了一个基于 Azure BlobStorage 的自定义分布式缓存,以优化页面速度。网站应该从缓存中传递缓存页面,直到缓存页面过期。此实现应该像现有的 DistributedInMemoryCache、DistributedRedisCache 或 NCacheDistributedCache 一样工作。 howto 在这里描述 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; }
}
}
其实你做的是两件不同的事情
关于响应缓存
services.AddResponseCaching()
和app.UseResponseCaching()
分别用来注册ObjectPoolProvider
和ResponseCachingMiddleware
,这个中间件负责缓存我们的Action端点,有[ResponseCache]
在上面。
这个中间件不依赖于我们上面提到的IDistributedCache
。因此,两个不相关的东西,他们无论如何都没有任何关系。
关于IDistributedCache
这是为 DistributedCache
设计的准系统,因为 .net 核心团队认为它非常适合大多数情况。 IDistributedCache
可以通过多种机制实现,包括 RMDB、DocumentDb、Redis 等,甚至在这种情况下,还有 blob 存储。但是,请记住,准系统对实现细节一无所知(如果我们不实现该机制,则创建一个滑动过期的入口选项什么也不做)。
例如:
MemoryCache
实例,它实现了 IMemoryCache
(可以在 Microsoft.Extensions.Caching.Memory 找到),具有扩展 Set<TItem>
处理存储数据的过程并为此设置选项条目(如过期时间)正如我们所见here.
所以,即使我在 IDistributedCache
上看到了你的实现,但我没有看到任何关于处理过期的东西,所以......那是行不通的......即使我们尝试直接使用它。
没有魔法,代码就是这样实现的。
我编写了一个基于 Azure BlobStorage 的自定义分布式缓存,以优化页面速度。网站应该从缓存中传递缓存页面,直到缓存页面过期。此实现应该像现有的 DistributedInMemoryCache、DistributedRedisCache 或 NCacheDistributedCache 一样工作。 howto 在这里描述 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; }
}
}
其实你做的是两件不同的事情
关于响应缓存
services.AddResponseCaching()
和app.UseResponseCaching()
分别用来注册ObjectPoolProvider
和ResponseCachingMiddleware
,这个中间件负责缓存我们的Action端点,有[ResponseCache]
在上面。
这个中间件不依赖于我们上面提到的IDistributedCache
。因此,两个不相关的东西,他们无论如何都没有任何关系。
关于IDistributedCache
这是为 DistributedCache
设计的准系统,因为 .net 核心团队认为它非常适合大多数情况。 IDistributedCache
可以通过多种机制实现,包括 RMDB、DocumentDb、Redis 等,甚至在这种情况下,还有 blob 存储。但是,请记住,准系统对实现细节一无所知(如果我们不实现该机制,则创建一个滑动过期的入口选项什么也不做)。
例如:
MemoryCache
实例,它实现了 IMemoryCache
(可以在 Microsoft.Extensions.Caching.Memory 找到),具有扩展 Set<TItem>
处理存储数据的过程并为此设置选项条目(如过期时间)正如我们所见here.
所以,即使我在 IDistributedCache
上看到了你的实现,但我没有看到任何关于处理过期的东西,所以......那是行不通的......即使我们尝试直接使用它。
没有魔法,代码就是这样实现的。