news 2026/2/7 18:17:36

【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/2/7 1:38:26

25年想转行网络安全?一篇带你了解真实的网安职场!

25年想转行网络安全&#xff1f;一篇带你了解真实的网安职场&#xff01; 最近是不是经常刷到网络安全相关的内容&#xff1f;看着别人做渗透测试、参加CTF比赛&#xff0c;觉得这行挺酷&#xff0c;薪资也不错&#xff0c;心里痒痒的想转行&#xff1f;别急&#xff0c;今天咱…

作者头像 李华
网站建设 2026/2/3 0:04:46

软件测试工程师的职业导航罗盘——如何建立你的个人顾问委员会

在快速迭代的软件行业中&#xff0c;软件测试工程师常面临技术更迭迅速、职业路径多元化的挑战。建立"个人职业顾问委员会"&#xff08;Personal Board of Directors&#xff09;正是一种战略性的职业发展方法&#xff0c;它借鉴企业董事会的协同决策模式&#xff0c…

作者头像 李华
网站建设 2026/2/3 0:04:47

基于GA遗传优化的电动汽车光储充电站容量配置算法matlab仿真

1.程序功能描述基于GA遗传优化的电动汽车光储充电站容量配置算法matlab仿真。通过运行基于 GA 的光储充电站容量配置算法&#xff0c;得到了最优的容量配置方案。与传统的容量配置方法相比&#xff0c;该方案在降低投资成本和运行成本方面具有明显的优势。同时&#xff0c;通过…

作者头像 李华
网站建设 2026/2/7 2:48:15

为什么说这本书是C++进阶的必读宝典?深度解析Effective C++第3版

为什么说这本书是C进阶的必读宝典&#xff1f;深度解析Effective C第3版 【免费下载链接】EffectiveC中文版第3版.pdf资源介绍 《Effective C 中文版第3版》是一本深入浅出的C进阶教程&#xff0c;由侯老精心翻译&#xff0c;被誉为C学习者的“第二本书”。本书涵盖了C编程的高…

作者头像 李华
网站建设 2026/2/5 11:45:28

见过哪些醍醐灌顶的Java代码:从“卧槽“到“原来如此“的顿悟

"代码写出来是给人看的&#xff0c;顺便能在机器上运行"——某位秃顶程序员还记得第一次看JDK源码时的那种震撼吗&#xff1f;就像刘姥姥进了大观园&#xff0c;眼花缭乱的同时不禁感叹&#xff1a;"原来代码还能这么写&#xff01;"今天咱们就来聊聊那些让…

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

WCDB编译问题终极指南:从诊断到解决的完整方案

WCDB编译问题终极指南&#xff1a;从诊断到解决的完整方案 【免费下载链接】wcdb Tencent/wcdb: 是一个基于 SQLite 的数据库引擎&#xff0c;它提供了高性能、高可用性、安全性的移动数据库解决方案。适合用于移动设备和嵌入式设备的数据库开发&#xff0c;特别是对于需要高性…

作者头像 李华