Asp.Net Core 轻松学-被低估的过滤器

 

前言

    过滤器,从咱们开始开发 Asp.Net 应用程序开始,就一直伴随在咱们左右;Asp.Net Core 提供多种类型的过滤器,以知足多种多样的业务应用场景;而且在 Asp.Net Core 自己,过滤器的应用也很是普遍;可是,在实际的业务场景中,大部分开发人员只使用到其中 1 到 2 种类型,固然,这其中大部分可能性是因为业务场景的适用性使然,本文尝试简单介绍 Asp.Net Core 中提供的各类过滤器,以及实际的应用场景,但愿对您有所帮助。git

1. 介绍

1.1 做用范围

过滤器的做用范围
每种不一样的过滤器都有实际的做用范围,有一些全局过滤器还有做用域的限制,这取决于应用开发者在定义和初始化过滤器的时候的选择,每一个过滤器自己处理任务的权限和功能都大不相同,可是他们都有一个共同点,就是经过特性标记的方式使用,好比如下代码,对一个 Action 使用了过滤器 CustomerActionFiltergithub

[CustomerActionFilter]
        public ActionResult<string> Get(int id) {
            return "value";
        }
1.2 过滤器的工做原理

原理解释
过滤器通常在 Asp.Net Core MVC 管道内运行,通常在操做执行以前(befor) 或者执行以后(after) 执行,以供开发者能够选择在不一样的执行阶段介入处理api

1.3 过滤器类型,看下图

类型介绍
上图既是 Asp.Net Core 内置的各类过滤器类型,也是其执行优先级顺序,相同类型的过滤器还能够定义在某个阶段执行的顺序跨域

  1. 受权过滤器 AuthorizeAttribute
  2. 资源过滤器 IResourceFilter
  3. 异常过滤器 IExceptionFilter
  4. 操做过滤器 ActionFilterAttribute
  5. 结果过滤器 IResultFilter

3. 受权过滤器

3.1 使用介绍数组

在请求到达的时候最早执行,优先级最高,主要做用是提供用户请求权限过滤,对不知足权限的用户,能够在过滤器内执行拒绝操做,俗称“管道短路”
*注意:该过滤器只有执行以前(befor),没有执行以后(after)的方法
一般状况下,不须要自行编写过滤器,由于该过滤器在 Asp.Net Core 内部已经有了默认实现,咱们须要作的就是配置受权策略或者实现本身的受权策略,而后由系统内置的受权过滤器调用受权策略便可
必须将该过滤器内部可能出现的异常所有处理,由于在受权过滤器以前,没有任何组件可以捕获受权过滤器的异常,一旦受权管理器内部发生异常,该异常将直接输出到结果中缓存

3.2 应用场景服务器

受权管理器 AuthorizeAttribute 位于 命名空间 Microsoft.AspNetCore.Authorization 内,使用方式很是简单,查看如下代码markdown

[Authorize]
    [Route("api/[controller]")]
    public class UserController : Controller
    {
        [AllowAnonymous]
        [HttpGet]
        public ActionResult<string> Get() {
            return "default";
        }

        [HttpPost]
        public ActionResult<string> Post() {
            return "default";
        }
    }

UserController 被应用了 Authorize 特性进行标记,表示对该控制器内的任意操做执行受权验证;可是单独对 Get 操做进行了受权经过对标记,即 AllowAnonymous ,表示容许匿名访问
这是很是经常使用的作法,在受权应用中,经常须要对部分操做进行单独的受权策略
关于受权过滤器,先介绍到这里,下一篇单独对受权过滤器进行演示,由于关于这块的内容,要讲的实在是太多了

4. 资源过滤器

但请求进入,经过受权过滤器后,接下来将执行资源过滤器(若是有定义),使用资源过滤器甚至能够改变绑定模型,还能够在资源过滤器中实现缓存以提升性能

4.1 资源管理器实现自接口 IResourceFilter 或者 IAsyncResourceFilter,如今咱们来实现一个资源过滤器,输出一行信息,看看执行顺序

public class CustomerResourceFilter : Attribute, IResourceFilter
    {
        public void OnResourceExecuted(ResourceExecutedContext context) {
            Console.WriteLine("==== OnResourceExecuted");
        }

        public void OnResourceExecuting(ResourceExecutingContext context) {
            Console.WriteLine("==== OnResourceExecuting");
        }
    }

4.2 对 HomeController 的操做应用该资源过滤器,看看对一个操做同时应用 CustomerActionFilter 和 CustomerResourceFilter ,他们的执行顺序是什么

[Route("api/[controller]")]
    [ApiController]
    public class HomeController : ControllerBase
    {
        [HttpGet]
        [CustomerActionFilter]
        [CustomerResourceFilter]
        public async Task<ActionResult<IEnumerable<string>>> Get()
        {
            return new string[] { "value1", "value2" };
        }
    }

4.3 启动程序,访问 http://localhost:5000/api/home,输出结果以下

能够看到,执行顺序和开篇的第一张图例一致,首先执行时资源过滤器的 OnResourceExecuting 方法,接着请求接入了 操做过滤器的 OnActionExecuting 方法,最后执行操做过滤器的 OnResultExecuting 方法,而后把请求交给资源过滤器的 OnResourceExecuted,最后返回到客户端
因此,从执行顺序能够看出,资源管理器的执行优先级老是高于操做过滤器
资源过滤器能够应用于控制器或者操做,而后基于其执行优先级的特色,开发员人员能够在资源过滤器中定义某些静态资源或者缓存直接将数据返回给客户端,并使其执行短路操做,减小后续管道请求步骤,以提升服务器响应性能

5. 异常过滤器

在服务器向客户端写入响应内容以前,若是系统引起了异常,异常过滤器能够捕获该异常,该过滤器做用于全局范围,这也是最经常使用的过滤器

5.1 建立一个异常过滤器

public class CustomerExceptionFilter : Attribute, IExceptionFilter
    {
        public void OnException(ExceptionContext context) {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("发生了异常:{0}", context.Exception.Message);
            Console.ForegroundColor = ConsoleColor.Gray;
        }
    }

5.2 将 CustomerExceptionFilter 应用到 HomeController 上

请注意,HomeController 上还同时应用了资源过滤器;如今要作到就是在资源过滤器内部抛出异常,看看 CustomerExceptionFilter 是否能够捕获该异常

public class CustomerResourceFilter : Attribute, IResourceFilter
    {
        public void OnResourceExecuted(ResourceExecutedContext context) {
            Console.WriteLine("==== OnResourceExecuted");
        }

        public void OnResourceExecuting(ResourceExecutingContext context) {
            Console.WriteLine("==== OnResourceExecuting");
            throw new Exception("资源管理器发生了异常");
        }
    }

5.3 运行程序,访问 http://localhost:5000/api/home

能够看到,系统抛出了异常;可是,异常过滤器 CustomerExceptionFilter 并无捕获该异常,事实证实资源过滤器的执行优先级仍是高于异常过滤器,如今咱们尝试在操做内部引起异常

[Route("api/[controller]")]
    [ApiController]
    [CustomerResourceFilter]
    [CustomerExceptionFilter]
    public class HomeController : ControllerBase
    {
        // GET api/values
        [HttpGet]
        [CustomerActionFilter]
        public async Task<ActionResult<IEnumerable<string>>> Get()
        {
            throw new Exception("Get操做发生了异常");
            return new string[] { "value1", "value2" };
        }
    }

5.4 再次启动程序,访问 http://localhost:5000/api/home,控制台输出结果以下

5.5 客户端获得了一个友好的返回值

5.6 这是由于咱们在异常过滤器内部将异常进行了出来,并经过设置 context.ExceptionHandled = true 来标记表示异常已经被处理,而后输出友好信息

public class CustomerExceptionFilter : Attribute, IExceptionFilter
    {
        public void OnException(ExceptionContext context)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("发生了异常:{0}", context.Exception.Message);
            Console.ForegroundColor = ConsoleColor.Gray;

            context.Result = new JsonResult(new { code = 500, message = context.Exception.Message });
            context.ExceptionHandled = true;
        }
    }

异常过滤器的应用很是简单,你能够在其内部将异常写入日志,或者执行其它须要处理的逻辑

6. 操做过滤器 ActionFilterAttribute 和 结果过滤器 IResultFilter

  1. 操做过滤器:当请求进入 API 接口的时候,操做过滤器提供了一个进入以前(before)和进入以后(after)介入功能,可使用该过滤器对进入 API 的参数和结果进行干预
  2. 结果过滤器:这个过滤器的做用和操做过滤器很是类似,主要其做用范围是有微小区别的,结果过滤器是在操做即将返回结果到客户端以前(before)或者以后(after)执行干预,好比你能够在返回结果以后(after)去渲染视图

6.1 之因此将这两个过滤器放在一块儿讲,是由于,这两个过滤器就像一对孪生兄弟同样,正所谓善始善终,首先来看操做过滤器

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
    public abstract class ActionFilterAttribute : Attribute, IActionFilter, IFilterMetadata, IAsyncActionFilter, IResultFilter, IAsyncResultFilter, IOrderedFilter
    {
        protected ActionFilterAttribute();

        //
        public int Order { get; set; }

        //
        public virtual void OnActionExecuted(ActionExecutedContext context);
        //
        public virtual void OnActionExecuting(ActionExecutingContext context);
        //
        [AsyncStateMachine(typeof(<OnActionExecutionAsync>d__6))]
        public virtual Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next);
        //
        public virtual void OnResultExecuted(ResultExecutedContext context);
        //
        public virtual void OnResultExecuting(ResultExecutingContext context);
        //
        [AsyncStateMachine(typeof(<OnResultExecutionAsync>d__9))]
        public virtual Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next);
    }

操做过滤器包含 6 个基础方法,分别是执行前(before)执行后(after),写入结果前(before)写入后(after)
为何会这样呢,由于操做过滤器实现的接口中包含告终果过滤器的接口
根据官方的提示,若是你须要重写 ActionFilterAttribute 的方法以处理自定义的业务逻辑,那么 OnActionExecutionAsync 这个异步方法不该该和 执行前(before)执行后(after)同时共存
一样,写入结果前(before)写入后(after)和 OnResultExecutionAsync 也是同样

6.2 操做过滤器包含了 写入结果前(before)写入后(after)的方法,这使得咱们能够不用去定义结果过滤器就能够实现对写入结果的管理

固然,最好的作法是定义结果过滤器,这有助于业务分类,且逻辑清晰明了,可是若是你但愿可使用异步操做,很遗憾,结果过滤器不支持该方法

6.3 下面来看结果过滤的定义

public class CustomerResultFilter : Attribute, IResultFilter
    {
        public void OnResultExecuted(ResultExecutedContext context) {
            Console.WriteLine("OnResultExecuted");
        }

        public void OnResultExecuting(ResultExecutingContext context) {
            Console.WriteLine("OnResultExecuting");
        }
    }

代码很是简单,就是实现接口 IResultFilter
IResultFilter 的工做原理和操做过滤器的写入结果前(before)写入后(after)的方法执行一致,能够看到,他们两个方法和参数名称都是一致的,由于他们都是实现同一个接口 IResultFilter

6.4 利用结果过滤器实现对输出结果的干预

下面就简单在结果过滤器内部去对已经组织好的数据进行干预,HomeController.Get 方法本应该输出 一个数组,咱们在Header 中增长一项输出:Author=From Ron.liang

public class CustomerResultFilter : Attribute, IResultFilter
    {
        public void OnResultExecuted(ResultExecutedContext context) {
            // ToDo
        }

        public void OnResultExecuting(ResultExecutingContext context) {
            // 干预结果
            context.HttpContext.Response.Headers.Add("Author", "From Ron.liang");
        }
    }

6.5 输出结果

7.在过滤器中使用依赖注入

在上面介绍的各类各样的过滤器中,有时候咱们可能须要读取程序运行环境的信息,根据不一样的环境作出不一样的响应内容
好比,上面的结果过滤器写入做者信息,可能咱们只但愿在开发环境输出,而在产品环境忽略

7.1 使用 GetService,以支持依赖注入

public void OnResultExecuting(ResultExecutingContext context)
        {
            var env = (IHostingEnvironment)context.HttpContext.RequestServices.GetService(typeof(IHostingEnvironment));

            Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine("OnResultExecuting,{0}", env.EnvironmentName);
            Console.ForegroundColor = ConsoleColor.Gray;

            // 干预结果
            if (env.IsDevelopment())
                context.HttpContext.Response.Headers.Add("Author", "From Ron.liang");
        }

上面的从 context.HttpContext.RequestServices.GetService(typeof(IHostingEnvironment)) 获取了环境变量,并判断在开发环境下为响应头添加内容

7.2 在过滤器中使用中间件

Asp.Net Core 提供了一个功能,使得咱们在过滤器中可使用中间件,实际上,这二者的使用方式很是相似
若是你但愿这么作,能够定义一个包含 Configure(IApplicationBuilder applicationBuilder) 方法的类,在控制器或者操做中使用它

7.3 定义注册管理管道类

public class RegisterManagerPipeline {
        public void Configure(IApplicationBuilder applicationBuilder) {
            CookieAuthenticationOptions options = new CookieAuthenticationOptions();

            applicationBuilder.UseCors(config =>
            {
                config.AllowAnyOrigin();
            });
        }
    }

RegisterManagerPipeline 定义了一个 Configure 方法,在该方法内部执行一个跨域设置,表示容许任何来源访问该站点;而后,咱们在 UserController 中应用该管道

[Authorize]
    [Route("api/[controller]")]
    [MiddlewareFilter(typeof(RegisterManagerPipeline))]
    public class UserController : Controller
    {
        // GET: api/<controller>
        [AllowAnonymous]
        [HttpGet]
        public ActionResult<string> Get() {
            return "default";
        }
    }

应用方式很是简单,就像使用普经过滤器同样对控制器进行特性标记便可
所不一样的是,这里使用的是 MiddlewareFilter 进行注册 RegisterManagerPipeline
管道式过滤器的优先级很是高,甚至比受权过滤器的优先级还高,在使用的时候须要特别注意应用场景

8. 过滤器的执行顺序

相同类型的过滤器其执行顺序可使用 Order 字段进行指定,该值为一个 int32 类型,值越小表示优先级越高,该值只能做用于相同类型的过滤器
好比,定义了两个 ActionFilter ,UserNameActionFilter,UserAgeActionFilter,分别制定其 Order 字段值为 10,5,那么 UserAgeActionFilter 将会在调用 ,UserNameActionFilter 以前执行
可是,即便指定了 Order ,ActionFilter 的执行优先级也不会超越受权管理器 AuthorizeAttribute,这是设计上的不一样

8.1 Order 演示代码

[HttpPost]
        [UserNameActionFilter(Order = 10)]
        [UserAgeActionFilter(Order = 5)]
        public void Post([FromBody] UserModel value) {
        }

8.2 输出结果

上图输出的黄色部分文字清晰的说明了过滤器的执行顺序
显示执行了资源过滤器,接着执行了 Order=5 的 UserAgeActionFilter ,最后执行了 Order=10 的 UserNameActionFilter 过滤器
能够看到,虽然操做过滤器设置了 Order=5,但其执行优先级仍然不能超越受权过滤器,甚至没法超越资源过滤器

结束语

  • 本文简单介绍了 Asp.Net Core 下系统内置的各类各样的过滤器,分别是

    1. 受权过滤器 AuthorizeAttribute
    2. 资源过滤器 IResourceFilter
    3. 异常过滤器 IExceptionFilter
    4. 操做过滤器 ActionFilterAttribute
    5. 结果过滤器 IResultFilter
  • 还经过一些简单说实例演示了过滤器的执行过程
  • 最后介绍了如何在过滤器中使用中间件,以及对过滤器的执行顺序进行了详细的演示

演示代码下载

https://github.com/lianggx/EasyAspNetCoreDemo/tree/master/Ron.FilterDemo

相关文章
相关标签/搜索