I wrote a custom distributed cache, based on Azure BlobStorage, to optimized page speed. The website should deliver the cached page from cache, till cached page has been expired. This implementation should work like the existing DistributedInMemoryCache, DistributedRedisCache ot NCacheDistributedCache. The howto is descript here https://learn.microsoft.com/de-de/aspnet/core/performance/caching/distributed?view=aspnetcore-5.0
My problem is, the methods to get or set in my cache aren't executed.
I implemented the IDistributedCache in as DistributedBlobStorageCache and registered it with helping of a ServiceCollection extension AddDistributedBlobStorageCache(). So fare so good.
The action has the ResponseCacheAttribute above and the cache profile is setup in Startup.cs. From my understanding, is the system correct configured, but the Get/GetAsync or Set/SetAsync method of distributed cache are executed.
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; }
}
}