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

前言

????过滤器,从我们开始开发 Asp.Net 应用程序开始,就一直伴随在我们左右;Asp.Net Core 提供多种类型的过滤器,以满足多种多样的业务应用场景;并且在 Asp.Net Core 本身,过滤器的应用也非常广泛;但是,在实际的业务场景中,大部分开发人员只使用到其中 1 到 2 种类型,当然,这其中大部分可能性是由于业务场景的适用性使然,本文尝试简单介绍 Asp.Net Core 中提供的各种过滤器,以及实际的应用场景,希望对您有所帮助。

1. 介绍

1.1 作用范围

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

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

原理解释
过滤器一般在 Asp.Net Core MVC 管道内运行,一般在操作执行之前(befor) 或者执行之后(after) 执行,以供开发者可以选择在不同的执行阶段介入处理

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 内,使用方式非常简单,查看以下代码

    [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://files.cnblogs.com/files/viter/Ron.FilterDemo.zip

原文地址:https://www.cnblogs.com/viter/p/10107886.html

时间: 2024-10-10 04:52:10

Asp.Net Core 轻松学-被低估的过滤器的相关文章

Asp.Net Core 轻松学-多线程之Task(补充)

前言 ????在上一章 Asp.Net Core 轻松学-多线程之Task快速上手 文章中,介绍了使用Task的各种常用场景,但是感觉有部分内容还没有完善,在这里补充一下. 1. 任务的等待 在使用 Task 进行基于队列的异步任务(TAP)的时候,对于刚入门的同学来说,只是简单的了解了使用 Task 可以在后台处理异步任务,但是对于阻塞调用可能还有有一些不太明白,异步任务默认是不阻塞的执行过程,当一个 Task 被创建出来的时候,并没有被压入队列中,而是开始执行的时候,才会进入队列中:执行一个

Asp.Net Core 轻松学-使用MariaDB/MySql/PostgreSQL和支持多个上下文对象

前言 在上一篇文章中(Asp.Net Core 轻松学-10分钟使用EFCore连接MSSQL数据库)[https://www.cnblogs.com/viter/p/10243577.html],介绍了 EFCore 连接 MSSQL 的使用方法,在本章中,将继续介绍如何利用 EFCore 连接到 MariaDB/MySql 和 PostgreSQL 数据库,同时,在一个项目中,如何添加多个数据库上下文对象,并在业务中使用多个上下文对象,通过这两章的学习,你将掌握使用 EFCore 连接 MS

Asp.Net Core 轻松学-从安装环境开始

Asp.Net Core 介绍 ????Asp.Net Core是微软新一代的跨平台开发框架,基友 C# 语言进行开发,该框架的推出,意味着微软从系统层面正式进击 Linux 服务器平台:从更新速度开来看,微软在 Asp.Net Core 的开发上可谓不遗余力. ????从开发者社区看,Asp.Net Core 有多火热,那么在过去 10 年间,C# 的开发者就有多压抑,过去 10 年以来,以 C# 开发语言为主业的开发者,几乎只能游历于所谓的企业级开发,其实就是做 OA.ERP.CRM 等传统

Asp.Net Core 轻松学-HttpClient的演进和避坑

前言 ????在 Asp.Net Core 1.0 时代,由于设计上的问题, HttpClient 给开发者带来了无尽的困扰,用 Asp.Net Core 开发团队的话来说就是:我们注意到,HttpClient 被很多开发人员不正确的使用.得益于 .Net Core 不断的版本快速升级:解决方案也一一浮出水面,本文尝试从各个业务场景去剖析 HttpClient 的各种使用方式,从而在开发中正确的使用 HttpClient 进行网络请求. 1.0时代发生的事情 1.1 在 1.0 时代,部署在 L

Asp.Net Core 轻松学-玩转配置文件

目录 前言 另类方式使用 hosting.json 使程序运行于多个端口 结语 前言 ????在 .NET Core 项目中,配置文件有着举足轻重的地位:与.NetFramework 不同的是,.NET Core 的配置文件都以 .json 结尾,这表示一个标准的 json 格式的文件:一个标准的 Asp.Net Core MVC 项目,一定带着一个 appsettings.json 文件,该文件便是项目默认配置文件,这和基于 .NetFramework 创建的 Asp.Net Web Appl

Asp.Net Core 轻松学-在.Net Core 中使用钩子

前言 ????Host startup hook,是2.2中提供的一项新的功能,通过使用主机启动钩子,允许开发人员在不修改代码的情况下,在服务启动之前注入代码:通过使用钩子,可以对已部署好的服务在服务启动期间自定义托管程序的行为:通过使用钩子,可以对服务进行跟踪或者遥测,也可以在服务启动前对托管环境进行健康检查:还可以通过钩子动态加载程序集进行依赖注入等功能. 什么是钩子 钩子的作用原理是通过设置环境变量 DOTNET_STARTUP_HOOKS 的值将钩子程序挂载到托管程序之中,在托管程序启动

Asp.Net Core 轻松学-多线程之取消令牌

前言 ????取消令牌(CancellationToken) 是 .Net Core 中的一项重要功能,正确并合理的使用 CancellationToken 可以让业务达到简化代码.提升服务性能的效果:当在业务开发中,需要对一些特定的应用场景进行深度干预的时候,CancellationToken 将发挥非常重要的作用. 1. 多线程请求合并数据源 在一个很常见的业务场景中,比如当请求一个文章详细信息的时候,需要同时加载部分点赞用户和评论内容,这里一共有 3 个任务,如果按照常规的先请求文章信息,

Asp.Net Core 轻松学-经常使用异步的你,可能需要看看这个文章

前言 事情的起因是由于一段简单的数据库连接代码引起,这段代码从语法上看,是没有任何问题:但是就是莫名其妙的报错了,这段代码极其简单,就是打开数据库连接,读取一条记录,然后立即更新到数据库中.但是,惨痛的事实证明,老司机也是会翻车的. 1. 异常的发生来得太突然 1.1 引起不舒适的代码片段 [HttpPut] public async void Put([FromBody] TopicViewModel model) { var topic = this.context.Topics.Where

asp.net core MVC 全局过滤器之ExceptionFilter过滤器(一)

本系类将会讲解asp.net core MVC中的内置全局过滤器的使用,将分为以下章节 asp.net core MVC 过滤器之ExceptionFilter过滤器(一) asp.net core MVC 过滤器之ActionFilter过滤器(二) asp.net core MVC 过滤器之ResultFilter过滤器(三) asp.net core MVC 过滤器之ResourceFilter过滤器(四) asp.net core MVC 过滤器之AuthorizationFilter过