翼度科技»论坛 编程开发 .net 查看内容

【ASP.NET Core】MVC过滤器:常见用法

5

主题

5

帖子

15

积分

新手上路

Rank: 1

积分
15
前面老周给大伙伴们演示了过滤器的运行流程,大伙只需要知道下面知识点即可:
1、过滤器分为授权过滤、资源访问过滤、操作方法(Action)过滤、结果过滤、异常过滤、终结点过滤。上一次咱们没有说异常过滤和终结点过滤,不过老周后面会说的。对这些过滤器,你有印象就行了。
2、所有过滤器接口都有同步版本和异步版本。为了让伙伴不要学得太累,咱们暂时只说同步版本的。
3、过滤器的应用可以分为全局和局部。全局先运行,局部后运行。全局在应用程序初始化时配置,局部用特性类来配置。
4、实际应用中,我们不需要实现所有过滤器接口,需要啥就实现啥即可。比如,你想在 Action 调用后修改一些东西,那实现 IActionFilter 接口就好了,其他不用管。
 
本篇咱们的重点在于“用”,光知道是啥是不行的,得拿来用才是硬道理。
我们先做第一个练习:阻止控制器的参数从查询字符串获取数据。
什么意思呢?咱们知道,MVC 在模型绑定时,会从 N 个 ValueProvider 中提取数据值,包括 QueryString、Forms、RouteData 等。其中,QueryString 就是URL的查询字符串。比如,咱们写一个这样的控制器:
  1. public class GameController : ControllerBase
  2. {
  3.     [HttpGet("game/play")]
  4.     public string Play(Game g)
  5.     {
  6.         if(ModelState.IsValid == false)
  7.         {
  8.             return "你玩个寂寞";
  9.         }
  10.         return $"你正在玩{g.Year}年出的《{g.GameName}》游戏";
  11.     }
  12. }
  13. public class Game
  14. {
  15.     /// <summary>
  16.     /// 游戏序列号
  17.     /// </summary>
  18.     public string? GameSerial { get; set; }
  19.     /// <summary>
  20.     /// 游戏名称
  21.     /// </summary>
  22.     public string? GameName { get; set; }
  23.     /// <summary>
  24.     /// 谁发行的
  25.     /// </summary>
  26.     public string? Publisher { get; set; }
  27.     /// <summary>
  28.     /// 哪一年发行的
  29.     /// </summary>
  30.     public int Year { get; set; }
  31. }
复制代码
这个通过 /game/play?gameserial=DDSBYCL-5K2FF&gamename=伏地魔三世&publisher=无德无能科技有限公司&year=2017 这样的URL就能传递数据给 g 参数。
这里我不做 HTML 页了,直接通过 MapGet 返回 HTML 内容。
  1. app.MapGet("/", async (HttpContext context) =>
  2. {
  3.     string html = """
  4.     <!DOCTYPE html>
  5.     <html>
  6.         <head>
  7.             <title>试试看</title>
  8.             
  9.         </head>
  10.         <body>
  11.             
  12.                 <label for="gserial">游戏序列号:</label>
  13.                 <input id="gserial" type="text" />
  14.             
  15.             
  16.                 <label for="gname">游戏名称:</label>
  17.                 <input id="gname" type="text" />
  18.             
  19.             
  20.                 <label for="pub">发行者:</label>
  21.                 <input type="text" id="pub" />
  22.             
  23.             
  24.                 <label for="year">发行年份:</label>
  25.                 <input id="year" type="text"/>
  26.             
  27.             
  28.                 <button onclick="reqTest()">确定</button>
  29.             
  30.             <p id="res"></p>
  31.             
  32.         </body>
  33.     </html>
  34.     """;
  35.     var response = context.Response;
  36.     response.Headers.ContentType = "text/html; charset=UTF-8";
  37.     await response.WriteAsync(html);
  38. });
复制代码
设置响应的 Content-Type 头时一定要指定字符集是 UTF-8 编码,这样可以免去 99.999% 的乱码问题。向服务器发送请求是通过 fetch 函数实现的。
比如,咱们在页面上填写:

然后点一下“确定”按钮,提交成功后服务将响应:
  1. 你正在玩2022年出的《法外狂徒大冒险》游戏
复制代码
模型绑定的数据是从查询字符串提取出来的。现在,咱们写一个过滤器,阻止 QueryStringValueProvider 提供查询字符串数据。而 QueryStringValueProvider 实例是由 QueryStringValueProviderFactory 工厂类负责创建的。因此,需要写一个过滤器,在模型绑定之前删除 QueryStringValueProviderFactory 对象,这样模型绑定时就不会读取 URL 中的查询字符串了。
于是,重点就落在选用哪种过滤器。关键点是:必须在模型绑定前做这项工作。所以,Action过滤器、结果过滤器就别指望了,而且肯定不是授权过滤器,那就剩下资源过滤器了。
咱们写一个自定义的资源过滤器—— RemoveQueryStringProviderFilter,实现的接口当然是 IResourceFilter 了。
  1. using Microsoft.AspNetCore.Mvc.Filters;
  2. using Microsoft.AspNetCore.Mvc.ModelBinding;
  3. public class RemoveQueryStringProviderFilter : IResourceFilter
  4. {
  5.     public void OnResourceExecuted(ResourceExecutedContext context)
  6.     {
  7.         // 空空如也
  8.     }
  9.     public void OnResourceExecuting(ResourceExecutingContext context)
  10.     {
  11.         var qsValueProviders = context.ValueProviderFactories.OfType<QueryStringValueProviderFactory>();
  12.         if (qsValueProviders != null && qsValueProviders.Any())
  13.         {
  14.             context.ValueProviderFactories.RemoveType<QueryStringValueProviderFactory>();
  15.         }
  16.     }
  17. }
复制代码
我们要做的事情是在模型绑定之前才有效,所以 OnResourceExecuted 方法不用管,留白即可。在 OnResourceExecuting 方法中,首先用 ValueProviderFactories.OfType 方法找出现有的 QueryStringValueProviderFactory 对象,若找到,用 RemoveType 方法删除。
把这个过滤器应用于全局。
  1. var builder = WebApplication.CreateBuilder(args);
  2. builder.Services.AddControllers(options =>
  3. {
  4.     options.Filters.Add<RemoveQueryStringProviderFilter>();
  5. });
  6. var app = builder.Build();
复制代码
现在,咱们再运行应用程序,输入游戏信息。

点击“确定”按钮后,发现服务未响应正确的内容。
  1. 你正在玩0年出的《》游戏
复制代码
由于无法从查询字符串中提取到数据,所以返回默认属性值。为什么不是返回“你玩个寂寞”呢?因为模型绑定并没有出错,也未出现验证失败的值,只是未提供值而已,即 ModelState.IsValid 的值依然是 true 的。
 
下面咱们看看异常过滤器怎么用。
异常过滤器最好定义为局部的,而非全局。使用局部过滤器的好处是针对性好,全局的话你用一个万能异常处理好像过于简单。当然了,如果你的项目可以这样做,就随便用。
这里我定义一个局部的异常过滤器,通过特性方式应用到操作方法上。
  1. [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
  2. public class CustExceptionFilterAttribute : Attribute, IExceptionFilter
  3. {
  4.     public void OnException(ExceptionContext context)
  5.     {
  6.         // 看看有没有异常
  7.         if (context.Exception is null || context.ExceptionHandled)
  8.             return;
  9.         // 有异常哦,修改一下返回结果
  10.         ContentResult result = new();
  11.         result.Content = "出错啦,伙计。错误消息:" + context.Exception.Message;
  12.         // 设置返回结果
  13.         context.Result = result;
  14.         // 标记异常已处理
  15.         context.ExceptionHandled = true;
  16.     }
  17. }
复制代码
首先,context 参数是一种上下文对象,它在多上异常过滤器间共享实例(你可能实现了很多个异常过滤器)。context.Exception 属性引用的是异常类对象;注意一个有趣的属性 ExceptionHandled,它是一个 bool 类型的值,表示“我这个异常过滤器是否已处理过了”。这个有啥用呢?由于上下文是共享的,当你的某个异常过滤器设置了 ExceptionHandled 为 true,那么,其他异常过滤器也可以读这个属性。这样就可以实现:啊,原来有人处理过这个异常了,那我就不处理了。即 A 过滤器处理异常后设置为已处理,B 过滤器可以检查这个属性的值,如果没必要处理就跳过。
下面写一个控制器,并在方法成员上应用前面定义的异常过滤器。
  1. public class AbcController : ControllerBase
  2. {
  3.     [HttpGet("calc/add"), CustExceptionFilter]
  4.     public int Add(int x, int y)
  5.     {
  6.         int r = x + y;
  7.         if(r >= 1000)
  8.         {
  9.             throw new Exception("计算结果必须在1000以内");
  10.         }
  11.         return r;
  12.     }
  13. }
复制代码
此处我设定抛出异常的条件是:x、y 相加结果大于或等于 1000。
咱们以 GET 方式调用,URL 为 /calc/add?x=599&y=699,这样会发生异常。服务器的响应为:

 
最后一个例子是结果过滤器。咱们要实现在响应消息中添加自定义 Cookie。
  1. public class SetCookieResultFilter : IResultFilter
  2. {
  3.     public void OnResultExecuted(ResultExecutedContext context)
  4.     {
  5.         // 不能在这里写 Cookie
  6.     }
  7.     public void OnResultExecuting(ResultExecutingContext context)
  8.     {
  9.         HttpResponse response = context.HttpContext.Response;
  10.         // 设置Cookie
  11.         response.Cookies.Append("X-VER", "3.0");
  12.     }
  13. }
复制代码
这里要注意,咱们要写 Cookie 必须在 OnResultExecuting 方法中处理,不能在 OnResultExecuted 方法中写。因为当 OnResultExecuted 方法执行时,响应消息头已经被锁定了,Cookie 是通过 set-cookie 标头实现的,此时无法修改 HTTP 头了,写 Cookie 会抛异常。所以只能在 OnResultExecuting  方法中写。
定义一个控制器。
  1. public class DemoController : ControllerBase
  2. {
  3.     [HttpGet("abc/test")]
  4.     public string Work() => "山重水复疑无路";
  5. }
复制代码
将自定义的结果过滤器添加为全局。
  1. var builder = WebApplication.CreateBuilder(args);
  2. builder.Services.AddControllers(options =>
  3. {
  4.     options.Filters.Add<RemoveQueryStringProviderFilter>();
  5. });
  6. var app = builder.Build();
复制代码
好,试试看。

 

来源:https://www.cnblogs.com/tcjiaan/archive/2023/12/02/17871967.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具