news 2026/4/15 14:59:45

【CSDN 专栏】C# ASP.NET控制器过滤器:自定义 ActionFilterAttribute 实战(避坑 + 图解)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【CSDN 专栏】C# ASP.NET控制器过滤器:自定义 ActionFilterAttribute 实战(避坑 + 图解)

目录

    • 一、先搞懂核心:ActionFilterAttribute 是什么?(生活类比 + 流程图)
      • 小节:过滤器 = 请求处理的 “安检 + 后勤”
    • 二、代码实战:自定义 ActionFilterAttribute 完整示例
      • 小节:从 0 到 1 写一个可用的自定义过滤器
      • 步骤 1:创建自定义过滤器类(继承 ActionFilterAttribute)
      • 步骤 2:注册 / 使用过滤器(3 种方式)
        • 方式 1:Action 级别(局部使用)
        • 方式 2:控制器级别(整个控制器生效)
        • 方式 3:全局注册(所有控制器 / Action 生效)
      • 步骤 3:测试效果
    • 三、常踩的坑(附解决方案 + 生活类比)
      • 小节:避开这些坑,过滤器才好用
    • 四、总结

作为ASP.NET开发者,控制器(Controller)是处理前端请求的 “中枢大脑”,而过滤器(Filter)则是给这个大脑加装的 “智能插件”—— 能在请求处理的不同阶段自动执行逻辑,比如日志记录、权限校验、参数校验等。其中,继承ActionFilterAttribute实现自定义过滤器是日常开发中最常用的方式,但新手很容易踩坑。本文就从代码实战、避坑指南、生活类比三个维度,把自定义ActionFilterAttribute讲透,让你少走弯路。

一、先搞懂核心:ActionFilterAttribute 是什么?(生活类比 + 流程图)

小节:过滤器 = 请求处理的 “安检 + 后勤”

先举个生活例子:你去餐厅吃饭,从进门到用餐结束的流程是「进门→选座→点餐→用餐→结账→离开」。对应ASP.NET中 Controller 处理请求的流程是「请求到达→Action 执行前→Action 执行→Action 执行后→结果返回前→响应返回」。
ActionFilterAttribute就像餐厅的 “服务管家”,可以在「Action 执行前」(比如核对预定信息)、「Action 执行后」(比如询问用餐体验)这两个核心节点插入自定义逻辑,不入侵核心的 “做菜(业务逻辑)” 流程,实现功能解耦。
控制器请求 + ActionFilter 执行流程图

前端发送请求
Controller接收请求
Action执行前:OnActionExecuting
执行目标Action方法
Action执行后:OnActionExecuted
返回响应给前端

ActionFilterAttribute核心重写方法(4 个,常用前 2 个):
1.OnActionExecuting:Action 执行前触发(前置逻辑);
2.OnActionExecuted:Action 执行后触发(后置逻辑);
3.OnResultExecuting:返回 Result 前触发;
4.OnResultExecuted:返回 Result 后触发。

二、代码实战:自定义 ActionFilterAttribute 完整示例

小节:从 0 到 1 写一个可用的自定义过滤器

我们以 “接口访问日志记录” 为例,实现一个自定义日志过滤器,记录每个接口的请求参数、执行时间、响应状态。

步骤 1:创建自定义过滤器类(继承 ActionFilterAttribute)

usingMicrosoft.AspNetCore.Mvc;usingMicrosoft.AspNetCore.Mvc.Filters;usingSystem;usingSystem.Diagnostics;usingSystem.Text;namespaceWebDemo.Filters{/// <summary>/// 自定义接口访问日志过滤器/// </summary>publicclassApiLogFilter:ActionFilterAttribute{// 记录接口执行耗时privateStopwatch_stopwatch;/// <summary>/// Action执行前:初始化计时器+记录请求信息/// </summary>/// <param name="context">Action执行上下文(包含请求、参数等)</param>publicoverridevoidOnActionExecuting(ActionExecutingContextcontext){// 1. 初始化计时器_stopwatch=Stopwatch.StartNew();// 2. 获取请求基础信息varhttpContext=context.HttpContext;varrequest=httpContext.Request;varcontrollerName=context.RouteData.Values["controller"]?.ToString();varactionName=context.RouteData.Values["action"]?.ToString();// 3. 拼接请求参数(GET/POST参数都获取)StringBuilderparamBuilder=newStringBuilder();// 获取GET参数foreach(varqueryinrequest.Query){paramBuilder.Append($"{query.Key}={query.Value}&");}// 获取POST表单参数(JSON参数需单独解析,这里简化演示)foreach(varforminrequest.Form){paramBuilder.Append($"{form.Key}={form.Value}&");}varparamStr=paramBuilder.ToString().TrimEnd('&');// 4. 打印日志(实际项目可写入日志框架如Serilog/NLog)Console.WriteLine($"【请求开始】[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] "+$"控制器:{controllerName}| 方法:{actionName}| "+$"请求地址:{request.Path}| 请求参数:{paramStr}");base.OnActionExecuting(context);}/// <summary>/// Action执行后:记录执行耗时+响应状态/// </summary>/// <param name="context">Action执行完成上下文</param>publicoverridevoidOnActionExecuted(ActionExecutedContextcontext){// 停止计时器_stopwatch.Stop();varelapsedMilliseconds=_stopwatch.ElapsedMilliseconds;// 获取响应状态varstatusCode=context.HttpContext.Response.StatusCode;stringerrorMsg=string.Empty;// 捕获Action执行异常if(context.Exception!=null){errorMsg=context.Exception.Message;// 若需要继续抛出异常,注释下面这行;若想吞掉异常,保留// context.ExceptionHandled = true;}varcontrollerName=context.RouteData.Values["controller"]?.ToString();varactionName=context.RouteData.Values["action"]?.ToString();// 打印执行结果日志Console.WriteLine($"【请求结束】[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] "+$"控制器:{controllerName}| 方法:{actionName}| "+$"执行耗时:{elapsedMilliseconds}ms | 响应状态码:{statusCode}| 异常信息:{errorMsg}");base.OnActionExecuted(context);}}}

步骤 2:注册 / 使用过滤器(3 种方式)

过滤器的使用分为 “全局注册”、“控制器级”、“Action 级”,按需选择:

方式 1:Action 级别(局部使用)
usingMicrosoft.AspNetCore.Mvc;usingWebDemo.Filters;namespaceWebDemo.Controllers{[ApiController][Route("api/[controller]")]publicclassUserController:ControllerBase{// 仅该Action生效[ApiLogFilter][HttpGet("GetUser")]publicIActionResultGetUser(intid){// 模拟业务逻辑System.Threading.Thread.Sleep(100);returnOk(new{Id=id,Name="张三",Age=25});}[HttpPost("AddUser")]publicIActionResultAddUser([FromBody]UserDtouser){// 该Action不记录日志returnOk("添加成功");}}publicclassUserDto{publicstringName{get;set;}publicintAge{get;set;}}}
方式 2:控制器级别(整个控制器生效)
[ApiController][Route("api/[controller]")][ApiLogFilter]// 控制器下所有Action都生效publicclassOrderController:ControllerBase{[HttpGet("GetOrder")]publicIActionResultGetOrder(intorderId){returnOk(new{OrderId=orderId,Amount=99.9});}}
方式 3:全局注册(所有控制器 / Action 生效)

在Program.cs中注册:

varbuilder=WebApplication.CreateBuilder(args);// 添加控制器服务builder.Services.AddControllers(options=>{// 全局注册自定义日志过滤器options.Filters.Add<ApiLogFilter>();});varapp=builder.Build();// 中间件配置...app.MapControllers();app.Run();

步骤 3:测试效果

调用/api/User/GetUser?id=1,控制台输出:

【请求开始】[2025-12-13 10:00:00] 控制器:User | 方法:GetUser | 请求地址:/api/User/GetUser | 请求参数:id=1 【请求结束】[2025-12-13 10:00:00] 控制器:User | 方法:GetUser | 执行耗时:102ms | 响应状态码:200 | 异常信息:

三、常踩的坑(附解决方案 + 生活类比)

小节:避开这些坑,过滤器才好用

新手使用ActionFilterAttribute最容易踩以下 6 个坑,每个坑都配生活类比和解决方案:

坑点生活类比解决方案
1.全局过滤器覆盖局部逻辑,导致预期外执行给所有餐厅菜品加辣(全局),但个别菜品要求不辣(局部),结果全辣1. 局部过滤器可通过Order属性调整执行顺序;2. 敏感逻辑优先用局部过滤器;3. 全局过滤器加开关(如配置文件控制是否生效)
2.未处理ActionExecutedContext.Exception,导致异常吞掉 / 重复处理餐厅服务员发现菜品有问题,既不反馈后厨,也不告诉顾客,导致问题被掩盖1. 异常需明确处理:要么context.ExceptionHandled = true吞掉并返回友好提示;要么不设置,让框架继续抛出;2. 过滤器中捕获异常后,务必记录日志
3.过滤器中获取 POST JSON 参数为空餐厅服务员想核对顾客的线上点餐信息,但没拿到订单数据(数据还没解析)1. OnActionExecuting中获取 JSON 参数需手动解析:var body = await context.HttpContext.Request.BodyReader.ReadAsync();var json = Encoding.UTF8.GetString(body.Buffer);2. 注意读取后重置流位置:context.HttpContext.Request.Body.Position = 0;
4.过滤器中使用异步逻辑但用了同步重写方法服务员同时接多个点餐请求,却用 “逐个处理” 的同步方式,导致卡顿重写异步版本方法:OnActionExecutingAsync/OnActionExecutedAsync,而非同步的OnActionExecuting/OnActionExecuted,示例:public override async Task OnActionExecutingAsync(ActionExecutingContext context, CancellationToken cancellationToken)
5.忽略Order属性,导致多个过滤器执行顺序混乱餐厅先上主食再上凉菜,不符合顾客 “先凉菜后主食” 的要求过滤器通过Order属性指定执行顺序(数值越小越先执行):[ApiLogFilter(Order = 1)]、[AuthFilter(Order = 0)](权限校验优先于日志)
6.过滤器中修改ModelState但未生效服务员发现顾客点的菜品售罄,却没告知收银台,导致订单仍提交 修改ModelState后,需手动设置context.Result终止后续流程:context.ModelState.AddModelError(“Name”, “姓名不能为空”);context.Result = new BadRequestObjectResult(context.ModelState);坑点实战示例(解决 “POST JSON 参数为空” 问题)修改ApiLogFilter的OnActionExecuting方法,支持获取 JSON 参数:
publicoverrideasyncvoidOnActionExecuting(ActionExecutingContextcontext){_stopwatch=Stopwatch.StartNew();varhttpContext=context.HttpContext;varrequest=httpContext.Request;varcontrollerName=context.RouteData.Values["controller"]?.ToString();varactionName=context.RouteData.Values["action"]?.ToString();StringBuilderparamBuilder=newStringBuilder();// 获取GET参数foreach(varqueryinrequest.Query){paramBuilder.Append($"{query.Key}={query.Value}&");}// 新增:获取POST JSON参数if(request.ContentType?.Contains("application/json")==true){// 读取请求体varstream=request.Body;varbuffer=newbyte[Convert.ToInt32(request.ContentLength)];awaitstream.ReadAsync(buffer,0,buffer.Length);varjsonParam=Encoding.UTF8.GetString(buffer);paramBuilder.Append($"JSON参数:{jsonParam}");// 重置流位置,避免后续ModelBinder读取不到参数request.Body.Position=0;}varparamStr=paramBuilder.ToString().TrimEnd('&');Console.WriteLine($"【请求开始】[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] "+$"控制器:{controllerName}| 方法:{actionName}| "+$"请求地址:{request.Path}| 请求参数:{paramStr}");base.OnActionExecuting(context);}

四、总结

自定义ActionFilterAttribute是ASP.NET中解耦横切逻辑(日志、权限、参数校验等)的 “利器”,核心是抓住「Action 执行前 / 后」两个核心节点,灵活选择全局 / 控制器 / Action 级使用方式。避开 “参数获取为空、异步逻辑同步写、执行顺序混乱” 等坑,就能让过滤器成为 Controller 的 “得力助手”,而非 “埋雷点”。

留言互动
你在项目中用ActionFilterAttribute实现过哪些功能?(比如权限校验、接口限流、参数脱敏等)有没有遇到过本文没提到的坑?欢迎在评论区分享,一起交流避坑技巧~
专栏说明:本文聚焦ActionFilterAttribute核心实战,后续会更新其他过滤器(如AuthorizationFilterAttribute权限过滤器、ExceptionFilterAttribute异常过滤器)的使用技巧,关注我,持续解锁ASP.NET进阶干货~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/13 6:02:52

InstantID技术揭秘:如何用AI在3分钟内实现真实人脸年龄变化?

你是否曾想象过&#xff0c;仅凭一张照片就能穿越时光&#xff0c;看到自己未来或过去的模样&#xff1f;这不再是科幻电影的情节&#xff0c;而是InstantID带来的现实。这项创新技术让复杂的人脸年龄变化模拟变得触手可及&#xff0c;无需专业背景&#xff0c;零代码操作&…

作者头像 李华
网站建设 2026/4/13 7:34:34

GP2040-CE终极指南:从零打造个性化游戏控制器的完整流程

想要打造专属的游戏控制器却不知从何下手&#xff1f;GP2040-CE开源固件项目让这一切变得简单。这个专为Pico-PIO-USB板设计的解决方案&#xff0c;为你打开了自定义游戏控制器的大门&#xff0c;无论你是格斗游戏爱好者还是平台跳跃高手&#xff0c;都能找到最适合自己的配置方…

作者头像 李华
网站建设 2026/4/11 12:50:25

入门】使用Node.js开发一个MCP服务器(STDIO方式)介绍

CP&#xff08;Model Control Protocol&#xff09;是一个标准化接口协议&#xff0c;用于定义AI工具的功能和参数格式。它允许AI以标准方式调用各种工具&#xff0c;例如通过定义参数格式&#xff08;如城市名称&#xff09;来获取城市天气信息。当用户请求查询北京天气时&…

作者头像 李华
网站建设 2026/4/13 15:28:59

实现一个深拷贝函数

基础问答问&#xff1a;知道浅拷贝和深拷贝吗&#xff1f;为什么要用深拷贝&#xff1f;答&#xff1a;拷贝&#xff0c;可以认为是赋值&#xff0c;对于 JavaScript 中的基础类型&#xff0c;如 string, number, null, boolean, undefined, symbol 等&#xff0c;在赋值给一个…

作者头像 李华
网站建设 2026/4/14 18:49:36

AI视频生成技术原理与行业应用 - AI视频技术原理与架构

AI视频技术原理与架构一、AI视频生成的核心逻辑与完整流程1. 核心定义&#xff1a;什么是AI视频生成模型&#xff1f;2. 完整流程&#xff1a;从数据到视频的四步走二、技术范式演进&#xff1a;从早期探索到主流架构1. 四大基础技术范式对比2. 范式演进时间线3. 主流架构&…

作者头像 李华