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  

更多分享,请大家关注我的个人公众号:

 

posted @ 2022-11-07 15:59  黄明辉  阅读(1232)  评论(0编辑  收藏  举报