ASP.NETCore MVC中过滤器的使用和总结
本篇文章介绍过滤器以下几点知识点
1、什么是过滤器
2、过滤器的执行流程
3、过滤器的作用域
4、过滤器的工作原理
5、过滤器的5种类型
6、取消和短路
7、过滤器的注入写法
8、同种过滤器自定义顺序
1、什么是过滤器?
.NET 中的过滤器(Filter)是 AOP(面向切面编程) 思想的一种实现,供我们在执行管道的特定阶段执行代码,通过使用过滤器可以实现 短路请求、缓存请求结果、日志统一记录、参数合法性验证、异常统一处理、返回值格式化 等等,同时使业务代码更加简洁单纯,避免很多重复代码。
2、MVC中过滤器执行流程
通过这个流程呢,我们可以看到它的执行顺序
从网络请求进来开始,先执行的是其他中间件(含自定义中间件,后面我们再另外写篇关于中间件应用的)---》路由中间件(MVC自带,路由选择)--》控制器中的Action--》然后才会执行过滤器。
反向的,当我们action中执行完业务代码后,请求一层层原路返回。
所以在我们的过滤器中,大部分过滤器有开始执行action,即ing 状态的方法,也有action业务代码执行完后触发的ed状态的方法。后面看例子就明白了
3、过滤器作用域
过滤器作用域设置是非常灵活的,可以选择为:
- 全局有效(整个 MVC 应用程序下的每一个 Action);
- 仅对某些 Controller 有效 (控制器内所有的 Action );
- 仅对某些 Action 有效;
4、过滤器的工作原理
过滤器一般在 Asp.Net Core MVC 管道内运行,一般在操作执行之前(befor) 或者执行之后(after) 执行,以供开发者可以选择在不同的执行阶段介入处理。
5、过滤器的类型
MVC中默认定义的有以下5中类型的过滤器
授权过滤器 AuthorizeAttribute
资源过滤器 IResourceFilter
异常过滤器 IExceptionFilter
操作过滤器 ActionFilterAttribute
结果过滤器 IResultFilter
5中过滤器的对比如下图:
同时,表中的过滤器从上到下,基本也是这5种过滤器的执行顺序。
我们可以同时定义多个甚至是5个类型的过滤器,不同类型的过滤器之间也存在这执行顺序的先后。
上图,就是5种过滤器在一个完整请求到响应的执行个过程。
通过顺序,我们可以看到它的执行先后(同一个过滤器,箭头有出有入的,表示有执行前(ing)和执行后(ed)两种状态的方法)
请求--》中间件(Middleware)--》授权过滤器--》资源过滤器ing--》Action 操作过滤器ing--》执行Action中的业务代码--》业务代码执行完,执行Action操作过滤器ed--》执行Result 结果过滤器--》执行资源过滤器ed方法--》返回中间件--》响应请求。
这是一个完整的路径。
还有另一条路径,差别在于当在Action中抛出异常时,不再执行Action 操作过滤器ed方法,而是执行异常过滤器(只有一个方法)--》执行资源过滤器ed方法--返回中间件--》响应请求
也就是当抛异常时,就不会执行action的ed方法和result过滤器了。
下面我们介绍5中过滤器的具体代码应用
MVC中过滤器都在命名空间:Microsoft.AspNetCore.Mvc.Filters 当中
所有过滤器我们都同时继承Attribute,方便我们把它当做特性来使用。
A:授权过滤器
授权过滤器,主要用于权限验证,验证当前是否登录,是否有权限访问某个资源。
授权过滤器在过滤器管道中第一个被执行,通常用于验证请求的合法性(通过实现接口 IAuthorizationFilter or IAsyncAuthorizationFilter) 在请求到达的时候最先执行,优先级最高,
主要作用是提供用户请求权限过滤,对不满足权限的用户,可以在过滤器内执行拒绝操作,俗称“管道短路”。
*注意:该过滤器只有执行之前(befor),没有执行之后(after)的方法 通常情况下,不需要自行编写过滤器,因为该过滤器在 Asp.Net Core 内部已经有了默认实现,我们需要做的就是配置授权策略或者实现自己的授权策略,然后由系统内置的授权过滤器调用授权策略即可 必须将该过滤器内部可能出现的异常全部处理,因为在授权过滤器之前,没有任何组件能够捕获授权过滤器的异常,一旦授权管理器内部发生异常,该异常将直接输出到结果中。
我们新建一个MVC项目,新建一个Filter 文件夹,存放我们定义的过滤器类。
项目环境,NETCore 3.1
定义一个授权过滤器,我们需要继承 IAuthorizationFilter
public class MyAuthorizationFilter :Attribute,IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { var reqeust = context.HttpContext.Request; var userId = reqeust.Cookies["userId"]; if(string.IsNullOrWhiteSpace(userId)) { throw new Exception("授权过滤器,您还没有登录"); } } }
授权过滤器只有一个方法,通过context对象我们可以获取当前请求的上下文对象,获取我们所需要的参数。
示例中,我们直接抛出异常,仅是为了方便,在实际项目中,我们还是需要友好的处理,此部分内容在下面的过滤器短路中介绍。
B:资源过滤器
资源过滤器在过滤器管道中第二个被执行,通常用于请求结果的缓存和短路过滤器管道(通过实现接口 IResourceFilter or IAsyncResourceFilter)
资源过滤器在实现缓存或短路过滤器管道尤其有用。
//我们以缓存为例 public class MyResourceFilter : Attribute, IResourceFilter { private readonly IMemoryCache _memoryCache; public MyResourceFilter(IMemoryCache memoryCache) { _memoryCache = memoryCache; } public void OnResourceExecuted(ResourceExecutedContext context) { //我们可以将当前的结果context.Result缓存起来,当执行ing时,直接返回,为了方便示例演示,我们用时间表示。 string content = "第一次的时间:" + DateTime.Now; _memoryCache.Set("key", content); } public void OnResourceExecuting(ResourceExecutingContext context) { var content = _memoryCache.Get("key"); //判断内存中是否有内容,有就直接返回,不再执行action过程。 if (content != null) { context.Result = new ContentResult() { Content= content.ToString()}; } } }
在实际的项目中,资源过滤器我们比较少会用到。
一般缓存我们都在action中用redis来实现。那在资源过滤器中做资源缓存,有啥好处呢?
节省资源开支,提高性能。
因为resourceing后面的就不用再执行了,通过context.Result直接返回,把整个请求短路了,后面的过程就不再执行了。
C:操作过滤器
Action 过滤器 要么实现 IActionFilter 接口,要么实现 IAsyncActionFilter 接口,它们可以在 action 方法执行的前后被执行。
Action 过滤器非常适合放置诸如查看模型绑定结果、或是修改控制器或输入到 action 方法的逻辑。
另外,action 过滤器可以查看并直接修改 action 方法的结果,即,可以对输出结果进行修改。
Action过滤器有两个方法,一个是action执行之前,一个是之后。他的应用 场景也很多。
比如我们写请求日志吧,一般我们需要记录时间、请求的客户端信息、请求的路由,当前控制器名称、action名称等。那就在action中实现吧。
public class MyActionFilter : Attribute, IActionFilter { public void OnActionExecuted(ActionExecutedContext context) { //有时我们视图页上需要显示当前登录账号的名称,我们可以通过下面代码获取,设置到ViewData对象中,视图中直接使用ViewData即可显示,替代传统的定义基类,每个控制器都要继承基类的写法。这也就是AOP啊 var controller = context.Controller as Controller; controller.ViewData["name"] = "张三"; } public void OnActionExecuting(ActionExecutingContext context) { //下面是演示怎么获取路由信息,context 对象里面还有更多惊喜,慢慢发掘吧 var ad = context.ActionDescriptor; //var ar= context.ActionArguments;//请求的参数信息 var str= ad.RouteValues["controller"] + "/" + ad.RouteValues["action"]; //为了方便演示,这边直接短路,输出路由名称和aciton名称 // context.Result = new ContentResult() { Content=str}; } }
D:结果过滤器
结果过滤器适用于任何需要直接环绕 View 或格式化处理的逻辑。结果过滤器可以替换或更改 Action 结果(而后者负责产生响应)
一般项目中很少用,这边不多介绍。
E:异常过滤器
用于实现常见的错误处理策略,没有之前和之后事件,处理 Razor 页面或控制器创建、模型绑定、操作过滤器或操作方法中发生的未经处理的异常。 但无法捕获资源过滤器、结果过滤器或 MVC 结果执行中发生的异常 。
这里很重要的一点就是,异常过滤器它步骤的异常范围有限,在中间件、或者授权、资源、结果过滤器中产生的异常,是步骤不到的。
也可以简单的理解为,它能捕捉的基本就是业务异常。
public class MyExceptionFilter : Attribute, IExceptionFilter { public void OnException(ExceptionContext context) { var ex = context.Exception; //这里可以写入异常日志 } }
6、取消和短路
这里的取消和短路是同一个意思,不同表达而已。
在过滤器中,我们可以做一些处理,但有时候,我们不希望请求继续往下走。
比如在授权过滤器中,我们既然知道了该用户未登录,是不能做任何处理的,那我们就可以给他短路,直接返回。这样可以提高我们的代码性能。
短路过滤器,有两种方式,其实在上面的代码示例中,都有用到了
第一种,就是直接抛出异常。但是这种很不友好。
如:
public void OnAuthorization(AuthorizationFilterContext context) { var reqeust = context.HttpContext.Request; var userId = reqeust.Cookies["userId"]; if(string.IsNullOrWhiteSpace(userId)) { throw new Exception("授权过滤器,您还没有登录"); } }
第二种就是使用context.Result直接返回结果。
如:
public void OnResourceExecuting(ResourceExecutingContext context) { var content = _memoryCache.Get("key"); //判断内存中是否有内容,有就直接返回,不再执行action过程。 if (content != null) { context.Result = new ContentResult() { Content= content.ToString()}; } }
这里的Result是指MVC中自带的几种视图的Result类型,包含如下图的类型
7、过滤器的注入方式
过滤器应该说有两种注入方式
一种是针对Controller控制器或者Action行为,直接加特性。表示控制范围为该控制器或者该Action
一种是全局的,直接在stratup.cs中全局注入。
第一种,就需要我们上面说的,过滤器要继承Attribute 特性,这样我们才可以当做特性使用。
比如
[MyResultFilter] public IActionResult Privacy() { return View(); }
第二种呢,直接上代码
public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(options=> { options.Filters.Add<Demo3.Filter.MyActionFilter>(); }); }
当时在实际使用中,我们会碰到一些情况,就是过滤器中带有带参的构造参数。
这时候的参数,就需要由注入端进行写入,通过DI依赖注入的方式获取。
我们定义一个Student学生类为例,然后将这个类注入。
public class Student { public string Name { get; set; } }
services.AddTransient<Student>();
public class MyResultFilter : Attribute, IResultFilter { private readonly Student _stu; /// <summary> /// 带参构造函数,实现对name的赋值 /// </summary> /// <param name="name"></param> public MyResultFilter(Student stu) { _stu = stu; } public void OnResultExecuted(ResultExecutedContext context) { } public void OnResultExecuting(ResultExecutingContext context) { context.Result = new ContentResult() { Content = _stu.Name }; } }
这时候,我们想在action上写MyResultFilter ,按上面的方式就不行了
也不能如下这么写
因为这个变量可能在其他业务逻辑中获取的
那这时,我们需要这么写
[TypeFilter(typeof(MyResultFilter))] public IActionResult Privacy() { return View(); }
后者全局,这么写
services.AddControllersWithViews(options=> { options.Filters.Add(typeof(Demo3.Filter.MyResultFilter)); });
8、同种过滤器的自定义顺序
上面我们讲到了,不同过滤器之间,有执行先后顺序,这个是MVC自带的
但是对于我们自定义的过滤器,也存在执行先后顺序的问题
比如我们引用了某个第三方组件,里面有一个action 过滤器了,这时候我们想优先执行我们自己定义的过滤器,或者都是我们自定义的过滤器,要有先后顺序,应该怎么写呢?
其实也很简单,定义过滤器时,继承 IOrderedFilter
public class MyResultFilter : Attribute, IResultFilter,IOrderedFilter { private readonly Student _stu; /// <summary> /// 带参构造函数,实现对name的赋值 /// </summary> /// <param name="name"></param> public MyResultFilter(Student stu) { _stu = stu; } public int Order { get; set; } public void OnResultExecuted(ResultExecutedContext context) { } public void OnResultExecuting(ResultExecutingContext context) { context.Result = new ContentResult() { Content = _stu.Name }; } }
在控制器或视图中我们可以这么写
[TypeFilter(typeof(MyResultFilter),Order =1)] public IActionResult Privacy() { return View(); }
services.AddControllersWithViews(options=> { options.Filters.Add(typeof(Demo3.Filter.MyResultFilter),1); });
这样就可以实现了过滤器的顺序配置。
以上就是介绍的全部内容。
其他类似文章,这篇还不错:https://blog.csdn.net/cplvfx/article/details/118118104
更多分享,请大家关注我的个人公众号: