ImageSharp自定义ImageProvider的Azure缓存失效问题排查
我基于.NET 6和ImageSharp做POC,要实现带Azure缓存的自定义ImageProvider。图片实际存储在SQL数据库,目前用静态文件模拟加载逻辑,需求是根据URL中的GUID从数据库加载图片并缓存到Azure Blob。
未找到该场景的教程,已在Program.cs中添加相关配置:
builder.Services.AddImageSharp() .Configure<AzureBlobStorageImageProviderOptions>(options => { // The "BlobContainers" collection allows registration of multiple containers. options.BlobContainers.Add(new AzureBlobContainerClientOptions { ConnectionString = appSettings["string"], ContainerName = "cache" }); }) .ClearProviders() .AddProvider<AzureBlobStorageImageProvider>() .Configure<AzureBlobStorageCacheOptions>(options => { options.ConnectionString = appSettings["string"]; options.ContainerName = "cache"; // Optionally create the cache container on startup if not already created. AzureBlobStorageCache.CreateIfNotExists(options, PublicAccessType.None); }).AddProvider<LoadImageFromDatabaseProvider>().SetCache<AzureBlobStorageCache>() .AddProvider<PhysicalFileSystemProvider>() .SetCache<AzureBlobStorageCache>();
自定义实现代码(POC版本):
namespace ImageService { public class LoadImageFromDatabaseProvider : IImageProvider { private readonly ImageRepository _imageRepository; /// <summary> /// A match function used by the resolver to identify itself as the correct resolver to use. /// </summary> private Func<HttpContext, bool> _match; public LoadImageFromDatabaseProvider(ImageRepository repository) { _imageRepository = repository; } public ProcessingBehavior ProcessingBehavior { get; } = ProcessingBehavior.All; public Func<HttpContext, bool> Match { get => _match ?? IsMatch; set => _match = value; } public async Task<IImageResolver> GetAsync(HttpContext context) { //Extract image name from the querystring e.g. /image?id=<imagei> string filename = Path.GetFileName(context.Request.Path); var id = filename.Split(".").FirstOrDefault(); if (!string.IsNullOrEmpty(id)) { var (image, metadata) = await _imageRepository.Get(id); return new ImageResolver(image, metadata); } return null; } public bool IsValidRequest(HttpContext context) => true; private bool IsMatch(HttpContext context) { return context.Request.Path.Value.Contains("productimages"); } } public class ImageResolver : IImageResolver { private byte[] _data; private Metadata _metadata; public ImageResolver(byte[] data, Metadata metadata) { _data = data; _metadata = metadata; } public Task<Stream> OpenReadAsync() { return Task.FromResult<Stream>(new MemoryStream(_data)); } Task<SixLabors.ImageSharp.Web.ImageMetadata> IImageResolver.GetMetaDataAsync() { return Task.FromResult(new SixLabors.ImageSharp.Web.ImageMetadata(_metadata.CreatedOn, _data.Length)); } } public class Metadata { public DateTime CreatedOn { get; internal set; } } public class ImageRepository { public ImageRepository() { } public async Task<(byte[] image, Metadata metadata)> Get(string id) { string path = Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\imgs\estonian-unicorn-in-space.jpg"); byte[] bytes = System.IO.File.ReadAllBytes(path); return (bytes, new Metadata() { CreatedOn = DateTime.Now }); } } }
当前问题:首次加载图片时已将其缓存到Azure Blob,但后续请求仍从自定义ImageProvider获取图片,且Azure中缓存图片的时间戳会更新,请问缓存未生效缺少哪些配置?
1. 调整Provider注册顺序(核心)
ImageSharp会按Provider注册顺序匹配请求,必须把AzureBlobStorageImageProvider放在最前面,让请求优先检查Azure缓存是否存在,只有缓存不存在时才会走自定义Provider加载源图片。
2. 移除重复的SetCache调用
你连续两次调用SetCache<AzureBlobStorageCache>(),重复调用会导致逻辑冲突,只需要在配置完缓存后调用一次即可。
3. 修正缓存键的生成逻辑(关键)
当前ImageRepository每次返回的Metadata.CreatedOn都是DateTime.Now,这会导致每次请求的图片元数据创建时间都不一样,ImageSharp生成的缓存键也会随之变化,所以每次都会重新生成缓存。需要把CreatedOn改成图片的固定创建时间(比如从数据库读取的实际值,或者本地文件的创建时间)。
4. 优化IsValidRequest逻辑
当前自定义Provider的IsValidRequest直接返回true,会匹配所有请求,即使缓存已经存在。可以结合Match逻辑,让它只匹配包含productimages的路径,避免和缓存Provider的匹配范围重叠。
修改后的代码示例
调整后的Program.cs配置
builder.Services.AddImageSharp() // 先配置并设置缓存 .Configure<AzureBlobStorageCacheOptions>(options => { options.ConnectionString = appSettings["string"]; options.ContainerName = "cache"; AzureBlobStorageCache.CreateIfNotExists(options, PublicAccessType.None); }) .SetCache<AzureBlobStorageCache>() // 优先注册缓存Provider .AddProvider<AzureBlobStorageImageProvider>() .Configure<AzureBlobStorageImageProviderOptions>(options => { options.BlobContainers.Add(new AzureBlobContainerClientOptions { ConnectionString = appSettings["string"], ContainerName = "cache" }); }) // 再注册自定义Provider和物理文件Provider .AddProvider<LoadImageFromDatabaseProvider>() .AddProvider<PhysicalFileSystemProvider>();
修改后的ImageRepository
public async Task<(byte[] image, Metadata metadata)> Get(string id) { string path = Path.Combine(Directory.GetCurrentDirectory(), @"wwwroot\imgs\estonian-unicorn-in-space.jpg"); byte[] bytes = System.IO.File.ReadAllBytes(path); // 用文件的实际创建时间代替当前时间,或者从数据库读取图片的创建时间 var fileInfo = new FileInfo(path); return (bytes, new Metadata() { CreatedOn = fileInfo.CreationTime }); }
内容的提问来源于stack exchange,提问作者Bjarke

