Asp.Net Core webapi+net6 使用资源筛选器(过滤器) 做缓存

写一个特性类,用来做标记

[AttributeUsage(AttributeTargets.Method)] //只对方法有效
public class ResourceFilterAttribute : Attribute
{

}

我这里使用了MemoryCache来做缓存,也可以使用字典来做,但一定要加上static,否则字典每一次请求都会new一个实例,缓存的东西就丢了

private static Dictionary<string,object> caCheDic=new Dictionary<string, object>();

过滤器代码实现

public class ResourceFilter : IAsyncResourceFilter
{
private readonly IMemoryCache cache;

public ResourceFilter(IMemoryCache cache)
{
this.cache = cache;
}
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{
//获取当前正在处理的控制器动作方法的相关信息,例如方法名、参数
var actionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;

// 检查当前请求是否为 Controller Action
if (actionDescriptor == null)
{
await next();
return;
}

// 检查当前 Action 是否包含 ResourceFilterAttribute,如果没有则继续处理下一个中间件
if (!actionDescriptor.MethodInfo.GetCustomAttributes(typeof(ResourceFilterAttribute), true).Any())
{
await next();
return;
}

 // 获取请求的IP
 var ip = context.HttpContext.Connection.RemoteIpAddress;
 // 获取方法名
 var methodName = actionDescriptor.ActionName;
 // 获取请求参数
 var parameters = context.ActionDescriptor.Parameters.Select(p => new
 {
     Name = p.Name,
     Value = context.HttpContext.Request.Query[p.Name].FirstOrDefault() ?? context.HttpContext.Request.Form[p.Name].FirstOrDefault()
 }).ToDictionary(p => p.Name, p => p.Value);

 // 把请求的IP、方法名和入参当做缓存的key
 var cacheKey = $"{ip}:{methodName}:{JsonConvert.SerializeObject(parameters)}";
//去缓存中找 如果有则直接返回
if (cache.TryGetValue(cacheKey, out IActionResult resultFromCache))
{
context.Result= resultFromCache;
return;
}

// 执行下一个中间件并获取结果
var resultContext =await next();

// 如果结果是 IActionResult 类型,则将结果缓存起来
if (resultContext.Result is IActionResult actionResult)
{
//缓存时间
var cacheOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromSeconds(10));
cache.Set(cacheKey, actionResult, cacheOptions);
}

context.Result = resultContext.Result;
}
}

在program类中要注入MemoryCache

builder.Services.AddMemoryCache();

还要进行配置我们刚才写的筛选器

builder.Services.AddControllers().AddMvcOptions(option => {
option.Filters.Add(typeof(ResourceFilter));
});

在需要进行缓存的action头上加上ResourceFilter特性,表示这个action的返回结果要进行缓存

[Route("api/[controller]/[action]"), ApiController]
public class TestController : ControllerBase
{
List<SysUser> sysUsers = new List<SysUser>()
{
new SysUser("admin","123"),
new SysUser("admin2","123")
};
[HttpGet, ResourceFilter]
public List<SysUser> GetUsers() { return sysUsers; }
}
public record SysUser(string loginName,string loginPwd);

进行测试,我这里直接就是截图的第二次请求,可以看到,请求去缓存里面读到了数据
image

还有需要注意的是,尽量不要在缓存中存储IQuerytable和IEnumtable等具有延迟执行的类型或接口的数据,因为是延迟执行,IQuerytable和IEnumtable都是生成的sql语句,所以在使用ef时,这些数据还是会去进行数据库操作,这样我们的缓存也就没有意义了。

——————————————————————————————————————————————————————————————————————
2024.2.29补充 踩坑

Value = context.HttpContext.Request.Query[p.Name].FirstOrDefault() ?? context.HttpContext.Request.Form[p.Name].FirstOrDefault()

这里不能在Query中找不到参数名相应的值后再去请求表单中找这样会报错

System.InvalidOperationException: This request does not have a Content-Type header. Forms are available from requests with bodies like POSTs and a form Content-Type of either application/x-www-form-urlencoded or multipart/form-data.

我是有一个Get请求(当然一般情况下只有Get请求的查询才会使用缓存),有一个参数是可空类型于是导致了这样的报错
这个错误会很影响自己的判断因为如果刚好请求的是Get的话,会一脸懵 Get请求怎么会说Content-Type的问题呢?
所以应该根据请求类型来取参数值

Value = GetParamValue(context, p.Name, context.HttpContext.Request.Method)

// 获取参数值的方法
private string GetParamValue(ResourceExecutingContext context, string paramName, string requestMethod)
{
    if (string.Equals(requestMethod, "GET", StringComparison.OrdinalIgnoreCase))
    {
        return context.HttpContext.Request.Query[paramName].FirstOrDefault();
    }
    else if (string.Equals(requestMethod, "POST", StringComparison.OrdinalIgnoreCase))
    {
        return context.HttpContext.Request.Form[paramName].FirstOrDefault();
    }
    else
    {
        // 其他请求方式,根据实际情况处理
        return "";
    }
}
posted @ 2023-11-07 15:32  柴油飞机  阅读(1076)  评论(2编辑  收藏  举报