所有分类
  • 所有分类
  • 游戏源码
  • 网站源码
  • 单机游戏
  • 游戏素材
  • 搭建教程
  • 精品工具

ASP.NET Core中ResourceFilter过滤器实现详解|实战步骤与原理全解析

ASP.NET Core中ResourceFilter过滤器实现详解|实战步骤与原理全解析 一

文章目录CloseOpen

这篇文章不想让你“只会复制代码”:我们从实战出发,一步步教你写自定义ResourceFilter类、配置Startup注册的具体步骤;再扒开原理讲清楚它的拦截时机(为什么比ActionFilter更早执行?)、生命周期选择(单例还是作用域?选错会出大问题!);甚至帮你避开“多次执行”“依赖注入冲突”的常见雷区。不管你是刚接触ASP.NET Core的新手想入门过滤器,还是老鸟想补全原理短板,读完就能把ResourceFilter的实现逻辑摸得明明白白——不仅知道“怎么做”,更懂“为什么要这么做”,再也不用对着代码猜“这一步到底起什么作用”。

你有没有过这种情况?在ASP.NET Core项目里想统一处理请求前的资源加载(比如缓存热门数据、打开文件流),结果要么拦截得太早——没经过权限验证就加载了资源,白费功夫;要么拦截得太晚——等模型绑定完才处理,浪费了性能;更糟的是加了过滤器之后,资源没释放导致服务器内存“蹭蹭涨”?其实这些问题的根源,都是没摸透ResourceFilter过滤器的“脾气”——它刚好卡在“权限验证之后、模型绑定之前”的黄金位置,专门解决“资源级”操作的痛点。今天我就把自己踩过的坑、实测有效的实现步骤和原理扒清楚,你跟着做就能避开90%的麻烦。

手把手教你实现:自定义ResourceFilter的3步实战

先直接上干货——实现一个能缓存热门接口响应的ResourceFilter,帮你节省数据库查询时间。我去年帮一个电商客户做商品列表页优化时,就用了这套逻辑,把接口响应时间从500ms降到了80ms,效果立竿见影。

  • 选对“同步/异步”:避免并发踩坑
  • ResourceFilter分两种实现方式:同步(IResourceFilter)异步(IAsyncResourceFilter)。要是你的操作里有异步逻辑(比如调用Redis缓存、数据库查询),一定要用异步版——我之前用同步版处理并发请求时,线程池直接被阻塞,导致请求超时,换成异步版立刻就好了。

    举个同步的例子:写一个CacheResourceFilter,拦截商品列表接口,先查缓存,有就直接返回,没有再走数据库:

    public class CacheResourceFilter IResourceFilter
    

    {

    private static readonly MemoryCache _cache = new MemoryCache(new MemoryCacheOptions());

    private readonly string _cacheKey;

    public CacheResourceFilter(string cacheKey)

    {

    _cacheKey = cacheKey;

    }

    // 请求阶段:先查缓存

    public void OnResourceExecuting(ResourceExecutingContext context)

    {

    if (_cache.TryGetValue(_cacheKey, out var cachedResult))

    {

    // 直接返回缓存的响应,跳过后续流程

    context.Result = new ContentResult

    {

    Content = cachedResult.ToString(),

    ContentType = "application/json",

    StatusCode = 200

    };

    }

    }

    // 响应阶段:缓存结果

    public void OnResourceExecuted(ResourceExecutedContext context)

    {

    if (context.Result is ObjectResult objectResult && !_cache.Contains(_cacheKey))

    {

    _cache.Set(_cacheKey, objectResult.Value, TimeSpan.FromMinutes(10));

    }

    }

    }

    要是你的操作里有异步调用(比如查Redis),就得用异步版(继承IAsyncResourceFilter):

    public class AsyncCacheResourceFilter IAsyncResourceFilter
    

    {

    private readonly IRedisCache _redisCache; // 假设你有Redis客户端

    private readonly string _cacheKey;

    public AsyncCacheResourceFilter(IRedisCache redisCache, string cacheKey)

    {

    _redisCache = redisCache;

    _cacheKey = cacheKey;

    }

    public async Task OnResourceExecutionAsync(

    ResourceExecutingContext context,

    ResourceExecutionDelegate next)

    {

    // 请求阶段:查Redis缓存

    var cachedData = await _redisCache.GetAsync(_cacheKey);

    if (!string.IsNullOrEmpty(cachedData))

    {

    context.Result = new ContentResult

    {

    Content = cachedData,

    ContentType = "application/json",

    StatusCode = 200

    };

    return;

    }

    // 没有缓存,执行后续流程(比如Action)

    var executedContext = await next();

    // 响应阶段:存缓存

    if (executedContext.Result is ObjectResult objectResult)

    {

    await _redisCache.SetAsync(_cacheKey, JsonConvert.SerializeObject(objectResult.Value), TimeSpan.FromMinutes(10));

    }

    }

    }

  • 注册过滤器:别忘“绑定生命周期”
  • 写好过滤器后,得把它“挂”到ASP.NET Core的管道里。有两种注册方式:全局注册(所有控制器/Action都生效)和局部注册(只给特定Action用)。

  • 全局注册:在Program.cs(或Startup.cs)里加一行代码——我帮客户项目做全局缓存时,就是这么配置的:
  •  var builder = WebApplication.CreateBuilder(args);
    

    builder.Services.AddControllersWithViews()

    .AddMvcOptions(options =>

    {

    // 注册同步缓存过滤器(全局生效)

    options.Filters.Add(new CacheResourceFilter("product:list"));

    // 注册异步缓存过滤器(需要依赖注入,所以用类型注册)

    options.Filters.Add();

    });

  • 局部注册:用
  • [ServiceFilter]特性给特定Action加过滤器——比如只给商品列表接口加缓存:

    csharp

    [HttpGet("products")]

    [ServiceFilter(typeof(AsyncCacheResourceFilter), Arguments = new object[] { "product:list" })]

    public IActionResult GetProducts()

    {

    // 业务逻辑

    }

    注意:用

    ServiceFilter的话,得先把过滤器注册成服务(比如builder.Services.AddScoped()),否则会报错“无法解析服务类型”——我第一次用的时候就忘这步,debug了半小时才找到问题。

    为什么ResourceFilter能解决你的问题?原理扒透

    很多人用ResourceFilter只知道“能拦截”,但不清楚它到底“特殊在哪”。其实它的核心优势,全藏在执行时机生命周期里。

  • 执行时机:卡在“黄金位置”的过滤器
  • ASP.NET Core的过滤器有固定的执行顺序,我整理了一张表,你一看就懂:

    过滤器类型 请求阶段执行顺序 响应阶段执行顺序
    AuthorizationFilter(权限验证) 1(最先) 5(最后)
    ResourceFilter(资源处理) 2 4
    ActionFilter(Action处理) 3 3
    ResultFilter(结果处理) 4 2
    ExceptionFilter(异常处理) 全程监听 全程监听

    看出来没?ResourceFilter的请求阶段在“权限验证之后、Action处理之前”——这意味着:

  • 它不会处理没通过权限验证的请求,避免白费资源;
  • 它在模型绑定之前执行(模型绑定是ActionFilter的前置操作),所以适合处理不需要模型的操作(比如缓存、资源加载)。
  • 比如你要缓存商品列表接口,用ResourceFilter在模型绑定前就返回缓存结果,比用ActionFilter(模型绑定后处理)省了“模型绑定”的性能开销——我客户的项目就是这么优化的,接口QPS从200涨到了500。

  • 生命周期:别用“单例”坑自己
  • ResourceFilter的默认生命周期是Scoped(作用域)——也就是每个请求创建一个过滤器实例。要是你改成Singleton(单例),得特别小心线程安全:单例实例会被所有请求共享,要是里面有可变状态(比如存用户ID),肯定会出问题。

    我之前有个项目,用单例的ResourceFilter存当前请求的用户信息,结果多个用户同时访问时,拿到的是别人的ID——后来改成Scoped生命周期,每个请求一个实例,问题立刻解决。微软官方文档也明确提醒:“如果过滤器依赖Scoped服务(比如DbContext),必须使用Scoped生命周期”(参考链接:微软ASP.NET Core过滤器文档)。

  • 和ActionFilter的核心区别:别用错场景
  • 很多人分不清ResourceFilter和ActionFilter,其实一句话就能说清:

  • ResourceFilter:处理“资源级”操作(不需要模型),比如缓存、文件流加载、数据库连接;
  • ActionFilter:处理“Action级”操作(需要模型),比如验证模型状态、修改响应结果。
  • 比如你要加载一个大文件,用ResourceFilter在请求开始时打开文件流,响应结束时关闭——要是用ActionFilter,得等模型绑定完才处理,文件流多开了几秒,浪费资源;再比如你要缓存接口响应,用ResourceFilter能跳过模型绑定,直接返回缓存,比ActionFilter快得多。

    避开这3个坑:我踩过的雷你别再踩

    最后再给你提3个我实测的“避坑技巧”,帮你少走弯路:

  • 同步过滤器:别忘在OnResourceExecuted里释放资源
  • 同步的ResourceFilter有两个方法:

    OnResourceExecuting(请求阶段)和OnResourceExecuted(响应阶段)。要是你在OnResourceExecuting里打开了资源(比如文件流、数据库连接),必须OnResourceExecuted里释放——我之前有个项目,用ResourceFilter加载Excel文件,结果忘了释放流,服务器内存从2G涨到8G,加上dispose后,内存立刻降到3G。

    csharp

    public void OnResourceExecuting(ResourceExecutingContext context)

    {

    // 打开文件流

    var fileStream = new FileStream(“data.xlsx”, FileMode.Open);

    context.Items[“FileStream”] = fileStream;

    }

    public void OnResourceExecuted(ResourceExecutedContext context)

    {

    // 释放文件流

    if (context.Items.ContainsKey(“FileStream”))

    {

    var fileStream = (FileStream)context.Items[“FileStream”];

    fileStream.Dispose();

    }

    }

    ### 
  • 异步过滤器:别阻塞线程
  • 要是你的异步过滤器里有同步操作(比如

    Task.Wait()Task.Result),会阻塞线程池,导致并发性能下降。比如我之前帮朋友做项目,在异步过滤器里用了_redisCache.GetAsync(_cacheKey).Result,结果并发高的时候,线程池满了,请求超时——换成await _redisCache.GetAsync(_cacheKey)就好了。

  • 全局注册:别影响“不需要的接口”
  • 要是你全局注册了ResourceFilter,比如缓存所有接口,记得给不需要缓存的接口加排除逻辑——比如用

    [IgnoreCache]特性,在过滤器里判断:

    csharp

    public void OnResourceExecuting(ResourceExecutingContext context)

    {

    // 跳过带[IgnoreCache]的Action

    if (context.ActionDescriptor.EndpointMetadata.Any(em => em is IgnoreCacheAttribute))

    {

    return;

    }

    // 缓存逻辑…

    }

    要是你按这些步骤做,基本能搞定ResourceFilter的90%问题。要是你试的时候遇到报错(比如注册失败、拦截时机不对),可以留个言,我帮你看看——毕竟我踩过的坑,比你见过的过滤器还多。


    同步和异步ResourceFilter该怎么选?

    主要看操作里有没有异步逻辑,比如要调用Redis缓存、查数据库这种需要等的操作,一定要用异步版(IAsyncResourceFilter)。我之前用同步版处理并发请求时,线程池直接被阻塞,请求超时,换成异步版立刻就好了。要是操作都是同步的(比如内存缓存),用同步版(IResourceFilter)也没问题,但尽量优先考虑异步,毕竟现在项目里异步逻辑越来越多,提前适配更稳妥。

    ResourceFilter的执行时机和ActionFilter有什么不一样?

    最核心的区别是执行顺序:ResourceFilter的请求阶段在“权限验证之后、模型绑定之前”,而ActionFilter在“模型绑定之后、Action执行之前”。比如你要缓存接口响应,用ResourceFilter能在模型绑定前就返回缓存结果,省了模型绑定的性能开销;要是用ActionFilter,得等模型绑定完才处理,就浪费了这部分性能。我之前帮电商客户优化商品列表接口时,用ResourceFilter把响应时间从500ms降到80ms,就是因为跳过了模型绑定这一步。

    ResourceFilter用单例还是作用域生命周期?

    默认用作用域(Scoped)就行,也就是每个请求创建一个过滤器实例。要是改成单例(Singleton),得特别小心线程安全——单例实例会被所有请求共享,要是里面有可变状态(比如存用户ID),肯定会出问题。我之前有个项目用单例的ResourceFilter存当前请求的用户信息,结果多个用户同时访问时,拿到的是别人的ID,后来改成作用域生命周期,每个请求一个实例,问题立刻解决。微软官方文档也明确提醒,如果过滤器依赖Scoped服务(比如DbContext),必须用Scoped生命周期。

    用ResourceFilter处理资源时,怎么避免内存泄漏?

    关键是“及时释放资源”。如果是同步ResourceFilter,要在OnResourceExecuted方法里释放——比如你在OnResourceExecuting里打开了文件流、数据库连接,就得在OnResourceExecuted里调用Dispose()。我之前有个项目打开Excel文件流后忘了释放,服务器内存从2G涨到8G,加了释放逻辑后立刻降到3G。如果是异步ResourceFilter,要在await next()之后处理释放,比如打开的资源流,得等后续流程执行完再关闭,别漏掉这一步。

    全局注册和局部注册ResourceFilter有什么区别?

    全局注册是所有控制器和Action都生效,比如在Startup.cs里用options.Filters.Add()配置,适合像全局缓存这种所有接口都要用的逻辑;局部注册是只给特定Action用,比如用[ServiceFilter]特性加在Action上,适合只需要给某几个接口用的情况。注意局部注册时,得先把过滤器注册成服务(比如builder.Services.AddScoped()),否则会报错“无法解析服务类型”——我第一次用的时候就忘这步,debug了半小时才找到问题。比如你只想给商品列表接口加缓存,用局部注册就很灵活,不用影响其他接口。

    原文链接:https://www.mayiym.com/48364.html,转载请注明出处。
    0
    显示验证码
    没有账号?注册  忘记密码?

    社交账号快速登录

    微信扫一扫关注
    如已关注,请回复“登录”二字获取验证码