注:本文隶属于《理解ASP.NET Core》系列文章,请查看置顶博客或点击此处查看全文目录

Filter概览

如果你是从ASP.NET一路走过来的,那么你一定对过滤器(Filter)不陌生。当然,ASP.NET Core仍然继承了过滤器机制。

过滤器运行在过滤器管道中,这是一张官方的图,很好地解释了过滤器管道在HTTP请求管道中的位置:

可以看到,只有当路由选择了MVC Action之后,过滤器管道才有机会执行。

过滤器不止一种,而是有多种类型。为了让各位对各过滤器执行顺序更容易理解一下,我把官方的图魔改了一下,如下:

  • Authorization Filters(授权过滤器):该过滤器位于所有过滤器的顶端,首先被执行。授权过滤器用于确认请求用户是否已授权,如未授权,则可以将管道短路,禁止请求继续传递。
  • Resource Filters(资源过滤器):当授权通过后,可以在过滤器管道的其他阶段(如模型绑定)之前和之后执行自定义逻辑
  • Action Filters(操作过滤器):在调用Action之前和之后执行自定义逻辑。通过操作过滤器,可以修改要传入Action的参数,也可以设置或修改Action的返回结果。另外,其也可以首先捕获到Action中抛出的未处理异常并进行处理。
  • Exception Filters(异常过滤器):当Controller创建时、模型绑定、Action Filters和Action执行中抛出未处理的异常时,异常过滤器可以捕获并进行处理,需要注意的是,在此之前,响应正文还未被写入,这意味着你可以设置返回结果。
  • Result Filters(结果过滤器):仅当Action的执行未抛出异常,或Action Filter处理了异常时,才会执行结果过滤器,允许你在操作结果执行之前和之后执行自定义逻辑。

东西有点多,你要忍一下。等看过下面的详细介绍之后,再来回顾上面,就很容易理解了。

这些过滤器,均实现了IFilterMetadata接口,该接口不包含任何行为,仅仅是用于标记这是MVC请求管道中的过滤器。

另外,如Resource Filters、Action Filters和Result Filters这种,他们拥有两个行为,分别在管道阶段的之前和之后执行,并按照习惯,将之前命名为OnXXXing,如 OnActionExecuting,将之后命名为OnXXXExecuted,如 OnActionExecuted

过滤器的作用域和注册方式

由于过滤器的种类繁多,为了方便大家边学习边测试,所以先介绍一下过滤器的作用域和注册方式。

过滤器的作用域范围和执行顺序

同样的,在介绍过滤器之前,先给大家介绍一下过滤器的作用域范围和执行顺序。

过滤器的作用域范围,可分为三种,从小到大是:

  • 某个Controller中的某个Action上(不支持Razor Page中的处理方法)
  • 某个Controller或Razor Page上
  • 全局,应用到所有Controller、Action和Razor Page上

不同过滤器的执行顺序,我们通过上面那幅图可以很清楚的知晓了,但是对于不同作用域的同一类型的过滤器,执行顺序又是怎样的呢?

IActionFilter举例说明,执行顺序为:

  • 全局过滤器的 OnActionExecuting

    • Controller和Razor Page过滤器的 OnActionExecuting

      • Action过滤器的 OnActionExecuting
      • Action过滤器的 OnActionExecuted
    • Controller和Razor Page过滤器的 OnActionExecuted
  • 全局过滤器的 OnActionExecuted

也就是说,对于不同作用域的同一类型的过滤器,执行顺序是由作用域范围大到小,然后再由小到大

过滤器的注册方式

接下来,看一下如何将过滤器注册为不同的作用域:

全局

注册为全局比较简单,直接配置MvcOptions.Filters即可:

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddMvc(options => options.Filters.Add<MyFilter>());
  4. // or
  5. services.AddControllers(options => options.Filters.Add<MyFilter>());
  6. // or
  7. services.AddControllersWithViews(options => options.Filters.Add<MyFilter>());
  8. }

Controller、Razor Page 或 Action

作用域为 Controller、Razor Page 或 Action 在注册方式上来说,实际上都是差不多的,都是以特性的方式进行标注。

最简单的,过滤器构造函数无参数或这些参数均无需由DI来提供,此时只需要过滤器继承Attribute即可:

  1. class MyFilterAttribute : Attribute, IActionFilter { }
  2. [MyFilter]
  3. public class HomeController : Controller { }

另一种,过滤器的构造函数参数均需要DI来提供,此时就需要用到ServiceFilterAttribute了:

  1. class MyFilter :IActionFilter
  2. {
  3. public MyFilter(IWebHostEnvironment env) { }
  4. }
  5. public void ConfigureServices(IServiceCollection services)
  6. {
  7. // 将过滤器添加到 DI 容器
  8. services.AddScoped<MyFilter>();
  9. }
  10. [ServiceFilter(typeof(MyFilter))]
  11. public class HomeController : Controller { }

ServiceFilterAttribute是如何创建这种类型过滤器的实例的呢?看下它的结构你就明白了:

  1. public interface IFilterFactory : IFilterMetadata
  2. {
  3. // 过滤器实例是否可跨请求重用
  4. bool IsReusable { get; }
  5. // 通过 IServiceProvider 创建指定过滤器类型的实例
  6. IFilterMetadata CreateInstance(IServiceProvider serviceProvider);
  7. }
  8. public class ServiceFilterAttribute : Attribute, IFilterFactory, IFilterMetadata, IOrderedFilter
  9. {
  10. // type 就是要创建的过滤器的类型
  11. public ServiceFilterAttribute(Type type)
  12. {
  13. ServiceType = type ?? throw new ArgumentNullException(nameof(type));
  14. }
  15. public int Order { get; set; }
  16. // 获取过滤器的类型,也就是构造函数中传进来的
  17. public Type ServiceType { get; }
  18. // 过滤器实例是否可跨请求重用,默认 false
  19. public bool IsReusable { get; set; }
  20. // 通过 IServiceProvider.GetRequiredService 创建指定过滤器类型的实例
  21. // 所以要求该过滤器和构造函数参数要在DI容器中注册
  22. public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
  23. {
  24. var filter = (IFilterMetadata)serviceProvider.GetRequiredService(ServiceType);
  25. if (filter is IFilterFactory filterFactory)
  26. {
  27. // 展开 IFilterFactory
  28. filter = filterFactory.CreateInstance(serviceProvider);
  29. }
  30. return filter;
  31. }
  32. }

如果你想要使过滤器实例在其作用域之外被重用,可以通过指定IsReusable = true来达到目的,需要注意的是要保证该过滤器所依赖的服务生命周期一定是单例的。另外,这并不能保证该过滤器实例是单例,也有可能出现多个。

好了,还有最后一种最复杂的,就是过滤器的构造函数部分不需要DI来提供,部分又需要DI来提供,此时就需要用到TypeFilterAttribute了:

  1. class MyFilter : IActionFilter
  2. {
  3. // 第一个参数 caller 不是通过DI提供的
  4. // 第二个参数 env 是通过DI提供的
  5. public MyFilter(string caller, IWebHostEnvironment env) { }
  6. }
  7. // ... 注意,这里就不需要将 MyFilter 注册到DI容器了,记得将注册代码删除
  8. // Arguments 里面存放的参数就是无需DI提供的参数
  9. [TypeFilter(typeof(MyFilter),
  10. Arguments = new object[] { "HomeController" })]
  11. public class HomeController : Controller { }

同样,看一下TypeFilterAttribute的结构:

  1. public class TypeFilterAttribute : Attribute, IFilterFactory, IFilterMetadata, IOrderedFilter
  2. {
  3. private ObjectFactory _factory;
  4. // type 就是要创建的过滤器的类型
  5. public TypeFilterAttribute(Type type)
  6. {
  7. ImplementationType = type ?? throw new ArgumentNullException(nameof(type));
  8. }
  9. // 要传递给过滤器构造函数的非DI容器提供的参数
  10. public object[] Arguments { get; set; }
  11. // 获取过滤器的类型,也就是构造函数中传进来的
  12. public Type ImplementationType { get; }
  13. public int Order { get; set; }
  14. public bool IsReusable { get; set; }
  15. // 通过 ObjectFactory 创建指定过滤器类型的实例
  16. public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
  17. {
  18. if (_factory == null)
  19. {
  20. var argumentTypes = Arguments?.Select(a => a.GetType())?.ToArray();
  21. _factory = ActivatorUtilities.CreateFactory(ImplementationType, argumentTypes ?? Type.EmptyTypes);
  22. }
  23. var filter = (IFilterMetadata)_factory(serviceProvider, Arguments);
  24. if (filter is IFilterFactory filterFactory)
  25. {
  26. // 展开 IFilterFactory
  27. filter = filterFactory.CreateInstance(serviceProvider);
  28. }
  29. return filter;
  30. }
  31. }

过滤器上下文

过滤器中的行为,都会有一个上下文参数,这些上下文参数都继承自抽象类FilterContext,而FilterContext又继承自ActionContext(这也从侧面说明了,过滤器就是为Action服务的):

  1. public class ActionContext
  2. {
  3. // Action相关的信息
  4. public ActionDescriptor ActionDescriptor { get; set; }
  5. // HTTP上下文
  6. public HttpContext HttpContext { get; set; }
  7. // 模型绑定和验证
  8. public ModelStateDictionary ModelState { get; }
  9. // 路由数据
  10. public RouteData RouteData { get; set; }
  11. }
  12. public abstract class FilterContext : ActionContext
  13. {
  14. public virtual IList<IFilterMetadata> Filters { get; }
  15. public bool IsEffectivePolicy<TMetadata>(TMetadata policy) where TMetadata : IFilterMetadata {}
  16. public TMetadata FindEffectivePolicy<TMetadata>() where TMetadata : IFilterMetadata {}
  17. }

当我们自定义一个过滤器时,免不了要和上下文进行交互,所以,了解上下文的结构,是不可或缺的。下面就挑两个重要的参数探究一下。

我们先来看ActionDescriptor,它里面包含了和Action相关的信息:

  1. public class ActionDescriptor
  2. {
  3. // 标识该Action的唯一标识,其实就是一个Guid
  4. public string Id { get; }
  5. // 路由字典,包含了controller、action的名字等
  6. public IDictionary<string, string> RouteValues { get; set; }
  7. // 特性路由的相关信息
  8. public AttributeRouteInfo? AttributeRouteInfo { get; set; }
  9. // Action的约束列表
  10. public IList<IActionConstraintMetadata>? ActionConstraints { get; set; }
  11. // 终结点元数据,咱们一般用不到
  12. public IList<object> EndpointMetadata { get; set; }
  13. // 路由中的参数列表,包含参数名、参数类型、绑定信息等
  14. public IList<ParameterDescriptor> Parameters { get; set; }
  15. public IList<ParameterDescriptor> BoundProperties { get; set; }
  16. // 过滤器管道中与当前Action有关的过滤器列表
  17. public IList<FilterDescriptor> FilterDescriptors { get; set; }
  18. // Action的个性化名称
  19. public virtual string? DisplayName { get; set; }
  20. // 共享元数据
  21. public IDictionary<object, object> Properties { get; set; }
  22. }

下面的HttpContext这个就不说了,太大了。不过你得知道,有了它,你可以针对请求和响应做自己想做的操作。

接下来就是ModelState,它是用于校验模型绑定的,通过它,可以知道模型是否绑定成功,也可以得到绑定失败的校验信息。相关细节将在后续关于模型绑定的文章中进行介绍。

然后就是RouteData,很显然,它存储了和路由有关的信息,那就看一下它包括什么吧:

  1. public class RouteData
  2. {
  3. // 当前路由路径上由路由生成的数据标记
  4. public RouteValueDictionary DataTokens { get; }
  5. // Microsoft.AspNetCore.Routing.IRouter 的实例列表
  6. public IList<IRouter> Routers { get; }
  7. // 路由值,包含了 ActionDescriptor.RouteValues 中的数据
  8. public RouteValueDictionary Values { get; }
  9. }

后面,就来到了Filters,看到IFilterMetadata我相信你也已经猜到了,它表示过滤器管道中与当前Action有关的过滤器列表。

Authorization Filters

授权过滤器是过滤器管道的第一个被执行的过滤器,用于系统授权。一般不会编写自定义的授权过滤器,而是配置授权策略或编写自定义授权策略。详细内容将在后续文章介绍。

Resource Filters

资源过滤器,在授权过滤器执行后执行,该过滤器包含“之前”和“之后”两个行为,包裹了模型绑定、操作过滤器、Action执行、异常过滤器、结果过滤器以及结果执行。

通过实现IResourceFilterIAsyncResourceFilter接口:

  1. public interface IResourceFilter : IFilterMetadata
  2. {
  3. void OnResourceExecuting(ResourceExecutingContext context);
  4. void OnResourceExecuted(ResourceExecutedContext context);
  5. }
  6. public interface IAsyncResourceFilter : IFilterMetadata
  7. {
  8. Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next);
  9. }

当拦截到请求时,你可以得到资源信息的上下文:

  1. public class ResourceExecutingContext : FilterContext
  2. {
  3. // 获取或设置该Action的执行结果
  4. public virtual IActionResult? Result { get; set; }
  5. // Action参数绑定源提供器工厂,比如 Form、Route、QueryString、JQueryForm、FormFile等
  6. public IList<IValueProviderFactory> ValueProviderFactories { get; }
  7. }
  8. public class ResourceExecutedContext : FilterContext
  9. {
  10. // 指示Action的执行是否已取消
  11. public virtual bool Canceled { get; set; }
  12. // 如果捕获到未处理的异常,会存放到此处
  13. public virtual Exception? Exception { get; set; }
  14. public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get; set; }
  15. // 指示异常是否已被处理
  16. public virtual bool ExceptionHandled { get; set; }
  17. // 获取或设置该Action的执行结果
  18. public virtual IActionResult? Result { get; set; }
  19. }

类似的,一旦设置了Result,就可以使过滤器管道短路。

对于ResourceExecutedContext,有两种方式来处理异常:

  • ExceptionExceptionDispatchInfo置为null
  • ExceptionHandled置为true

单纯的仅设置Result是行不通的。所以我建议大家,在处理异常时,除了设置Result外,也将ExceptionHandled设置为true,这样也让读代码的人更容易理解代码逻辑。

另外,ResourceExecutedContext.Canceled,用于指示Action的执行是否已取消。当在 OnResourceExecuting 中手动设置 ResourceExecutingContext.Result 时,会将 Canceled 置为 true。需要注意的是,想要测试这种情况,至少要注册两个资源过滤器,并且在第二个资源过滤器中设置Result,才能够在第一个过滤器中看到效果。

Action Filters

操作过滤器,在模型绑定后执行,该过滤器同样包含“之前”和“之后”两个行为,包裹了Action的执行(不包含Controller的创建)。

如果Action执行过程中或后续操作过滤器中抛出异常,首先捕获到异常的是操作过滤器的OnActionExecuted,而不是异常过滤器。

通过实现IActionFilterIAsyncActionFilter接口:

  1. public interface IActionFilter : IFilterMetadata
  2. {
  3. void OnActionExecuting(ActionExecutingContext context);
  4. void OnActionExecuted(ActionExecutedContext context);
  5. }
  6. public interface IAsyncActionFilter : IFilterMetadata
  7. {
  8. Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next);
  9. }

同样地,看一下上下文结构:

  1. public class ActionExecutingContext : FilterContext
  2. {
  3. // 获取或设置该Action的执行结果
  4. public virtual IActionResult? Result { get; set; }
  5. // Action的参数字典,key是参数名,value是参数值
  6. public virtual IDictionary<string, object> ActionArguments { get; }
  7. // 获取该Action所属的Controller
  8. public virtual object Controller { get; }
  9. }
  10. public class ActionExecutedContext : FilterContext
  11. {
  12. // 指示Action的执行是否已取消
  13. public virtual bool Canceled { get; set; }
  14. // 获取该Action所属的Controller
  15. public virtual object Controller { get; }
  16. // 如果捕获到未处理的异常,会存放到此处
  17. public virtual Exception? Exception { get; set; }
  18. public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get; set; }
  19. // 指示异常是否已被处理
  20. public virtual bool ExceptionHandled { get; set; }
  21. // 获取或设置该Action的执行结果
  22. public virtual IActionResult Result { get; set; }
  23. }

关于ActionExecutedContext.Canceled属性和异常处理相关的知识点,均与资源过滤器类似,这里就不再赘述了。

由于操作过滤器常常在应用中的使用比较频繁,所以这里详细介绍一下它的使用。ASP.NET Core框架提供了一个抽象类ActionFilterAttribute,该抽象类实现了多个接口,还继承了Attribute,允许我们以特性的方式使用。所以,一般比较建议大家通过继承该抽象类来自定义操作过滤器:

  1. [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
  2. public abstract class ActionFilterAttribute :
  3. Attribute, IActionFilter, IAsyncActionFilter, IResultFilter, IAsyncResultFilter, IOrderedFilter
  4. {
  5. public int Order { get; set; }
  6. public virtual void OnActionExecuting(ActionExecutingContext context) { }
  7. public virtual void OnActionExecuted(ActionExecutedContext context) { }
  8. public virtual async Task OnActionExecutionAsync(
  9. ActionExecutingContext context,
  10. ActionExecutionDelegate next)
  11. {
  12. // 删除了一些空校验代码...
  13. OnActionExecuting(context);
  14. if (context.Result == null)
  15. {
  16. OnActionExecuted(await next());
  17. }
  18. }
  19. public virtual void OnResultExecuting(ResultExecutingContext context) { }
  20. public virtual void OnResultExecuted(ResultExecutedContext context) { }
  21. public virtual async Task OnResultExecutionAsync(
  22. ResultExecutingContext context,
  23. ResultExecutionDelegate next)
  24. {
  25. // 删除了一些空校验代码...
  26. OnResultExecuting(context);
  27. if (!context.Cancel)
  28. {
  29. OnResultExecuted(await next());
  30. }
  31. }
  32. }

可以看到,ActionFilterAttribute同时实现了同步和异步接口,不过,我们在使用时,只需要实现同步或异步接口就可以了,不要同时实现。这是因为,运行时会先检查过滤器是否实现了异步接口,如果是,则调用该异步接口。否则,就调用同步接口。 如果在一个类中同时实现了异步和同步接口,则仅会调用异步接口。

当要全局进行验证模型绑定状态时,使用操作过滤器再合适不过了!

  1. public class ModelStateValidationFilterAttribute : ActionFilterAttribute
  2. {
  3. public override void OnActionExecuting(ActionExecutingContext context)
  4. {
  5. if (!context.ModelState.IsValid)
  6. {
  7. if (context.HttpContext.Request.AcceptJson())
  8. {
  9. var errorMsg = string.Join(Environment.NewLine, context.ModelState.Values.SelectMany(v => v.Errors.Select(e => e.ErrorMessage)));
  10. context.Result = new BadRequestObjectResult(AjaxResponse.Failed(errorMsg));
  11. }
  12. else
  13. {
  14. context.Result = new ViewResult();
  15. }
  16. }
  17. }
  18. }
  19. public static class HttpRequestExtensions
  20. {
  21. public static bool AcceptJson(this HttpRequest request)
  22. {
  23. if (request == null) throw new ArgumentNullException(nameof(request));
  24. var regex = new Regex(@"^(\*|application)/(\*|json)$");
  25. return request.Headers[HeaderNames.Accept].ToString()
  26. .Split(',')
  27. .Any(type => regex.IsMatch(type));
  28. }
  29. }

Exception Filters

异常过滤器,可以捕获Controller创建时(也就是只捕获构造函数中抛出的异常)、模型绑定、Action Filter和Action中抛出的未处理异常。

再着重说明一下:如果Action执行过程中或非首个操作过滤器中抛出异常,首先捕获到异常的是操作过滤器的OnActionExecuted,而不是异常过滤器。但是,如果在Controller创建时抛出异常,那首先捕获到异常的就是异常过滤器了。

我知道大家在初时异常过滤器的时候,有的人会误认为它可以捕获程序中的任何异常,这是不对的!

异常过滤器:

  • 通过实现接口IExceptionFilterIAsyncExceptionFilter来自定义异常过滤器
  • 可以捕获Controller创建时(也就是只捕获构造函数中抛出的异常)、模型绑定、Action Filter和Action中抛出的未处理异常
  • 其他地方抛出的异常不会捕获

先来看一下这两个接口:

  1. // 仅具有标记作用,标记其为 mvc 请求管道的过滤器
  2. public interface IFilterMetadata { }
  3. public interface IExceptionFilter : IFilterMetadata
  4. {
  5. // 当抛出异常时,该方法会捕获
  6. void OnException(ExceptionContext context);
  7. }
  8. public interface IAsyncExceptionFilter : IFilterMetadata
  9. {
  10. // 当抛出异常时,该方法会捕获
  11. Task OnExceptionAsync(ExceptionContext context);
  12. }

OnExceptionOnExceptionAsync方法都包含一个类型为ExceptionContext参数,很显然,它就是与异常有关的上下文,我们的异常处理逻辑离不开它。那接着来看一下它的结构吧:

  1. public class ExceptionContext : FilterContext
  2. {
  3. // 捕获到的未处理异常
  4. public virtual Exception Exception { get; set; }
  5. public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get; set; }
  6. // 指示异常是否已被处理
  7. // true:表示异常已被处理,异常不会再向上抛出
  8. // false:表示异常未被处理,异常仍会继续向上抛出
  9. public virtual bool ExceptionHandled { get; set; }
  10. // 设置响应的 IActionResult
  11. // 如果设置了结果,也表示异常已被处理,异常不会再向上抛出
  12. public virtual IActionResult? Result { get; set; }
  13. }

下面,我们就来实现一个自定义的异常处理器:

  1. public class MyExceptionFilterAttribute : ExceptionFilterAttribute
  2. {
  3. private readonly IModelMetadataProvider _modelMetadataProvider;
  4. public MyExceptionFilterAttribute(IModelMetadataProvider modelMetadataProvider)
  5. {
  6. _modelMetadataProvider = modelMetadataProvider;
  7. }
  8. public override void OnException(ExceptionContext context)
  9. {
  10. if (!context.ExceptionHandled)
  11. {
  12. // 此处仅为简单演示
  13. var exception = context.Exception;
  14. var result = new ViewResult()
  15. {
  16. ViewName = "Error",
  17. ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState)
  18. {
  19. // 记得给 ErrorViewModel 加上 Message 属性
  20. Model = new ErrorViewModel
  21. {
  22. Message = exception.ToString()
  23. }
  24. }
  25. };
  26. context.Result = result;
  27. // 标记异常已处理
  28. context.ExceptionHandled = true;
  29. }
  30. }
  31. }

接着,找到/Views/Shared/Error.cshtml,展示一下错误消息:

  1. @model ErrorViewModel
  2. @{
  3. ViewData["Title"] = "Error";
  4. }
  5. <p>@Model.Message</p>

最后,注册一下MyExceptionFilterAttribute

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddScoped<MyExceptionFilterAttribute>();
  4. services.AddControllersWithViews();
  5. }

现在,我们将该异常处理器加在/Home/Index上,并抛个异常:

  1. public class HomeController : Controller
  2. {
  3. [ServiceFilter(typeof(MyExceptionFilterAttribute))]
  4. public IActionResult Index()
  5. {
  6. throw new Exception("Home Index Error");
  7. return View();
  8. }
  9. }

当请求/Home/Index时,你会得到如下页面:

Result Filters

结果过滤器,包裹了操作结果的执行。所谓操作结果的执行,可以是Razor视图的处理操作,也可以是Json结果的序列化操作等。

通过实现IResultFilterIAsyncResultFilter接口:

  1. public interface IResultFilter : IFilterMetadata
  2. {
  3. void OnResultExecuting(ResultExecutingContext context);
  4. void OnResultExecuted(ResultExecutedContext context);
  5. }
  6. public interface IAsyncResultFilter : IFilterMetadata
  7. {
  8. Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next);
  9. }

当实现这两个接口其一时,则仅当Action或Action Filters生成Result时,才会执行结果过滤器。像授权、资源过滤器使管道短路或异常过滤器通过生成Result来处理异常等,都不会执行结果过滤器。

如果在 OnResultExecuting 中抛异常了,就会导致短路,Action结果和后续的结果过滤器都不会执行,并且执行结果也被视为失败。

同样地,看一下上下文结构:

  1. public class ResultExecutingContext : FilterContext
  2. {
  3. // 获取该Action所属的Controller
  4. public virtual object Controller { get; }
  5. // 获取或设置该Action的结果
  6. public virtual IActionResult Result { get; set; }
  7. // 指示结果过滤器是否应该被短路,若短路,Action结果和后续的的结果过滤器,都不会执行
  8. public virtual bool Cancel { get; set; }
  9. }
  10. public class ResultExecutedContext : FilterContext
  11. {
  12. // 指示结果过滤器是否被短路,若短路,Action结果和后续的的结果过滤器,都不会执行
  13. public virtual bool Canceled { get; set; }
  14. // 获取该Action所属的Controller
  15. public virtual object Controller { get; }
  16. // 获取或设置结果或结果过滤器执行过程中抛出的未处理异常
  17. public virtual Exception? Exception { get; set; }
  18. public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get; set; }
  19. // 异常是否已被处理
  20. public virtual bool ExceptionHandled { get; set; }
  21. // 获取或设置该Action的执行结果
  22. public virtual IActionResult Result { get; }
  23. }

可以通过继承抽象类ResultFilterAttribute来实现自定义结果过滤器:

  1. class MyResultFilter : ResultFilterAttribute
  2. {
  3. private readonly ILogger<MyResultFilter> _logger;
  4. public MyResultFilter(ILogger<MyResultFilter> logger)
  5. {
  6. _logger = logger;
  7. }
  8. public override void OnResultExecuted(ResultExecutedContext context)
  9. {
  10. context.HttpContext.Response.Headers.Add("CustomHeaderName", "CustomHeaderValue");
  11. }
  12. public override void OnResultExecuting(ResultExecutingContext context)
  13. {
  14. if (context.HttpContext.Response.HasStarted)
  15. {
  16. _logger.LogInformation("Response has started!");
  17. }
  18. }
  19. }

上面说过,IResultFilterIAsyncResultFilter接口有一定的局限性,当授权、资源过滤器使管道短路或异常过滤器通过生成Result来处理异常等,会导致结果过滤器不被执行。但是,如果在这种情况下,我们也想要执行结果过滤器,那该咋办呢?别慌,ASP.NET Core已经想到这种情况了。

那就是实现IAlwaysRunResultFilterIAsyncAlwaysRunResultFilter接口,看这名字就够直接了吧——始终运行:

  1. public interface IAlwaysRunResultFilter : IResultFilter, IFilterMetadata { }
  2. public interface IAsyncAlwaysRunResultFilter : IAsyncResultFilter, IFilterMetadata { }

中间件过滤器

中间件过滤器,其实是在过滤器管道中加入中间件管道。中间件过滤器的执行时机与资源过滤器一样,即模型绑定之前和管道的其余部分执行之后执行。

要创建中间件过滤器,需要满足一个条件,那就是该中间件必须包含一个Configure方法(一般来说还会包含一个IApplicationBuilder参数用于配置中间件管道,不过这不是强制的)。

例如:

  1. class MyPipeline
  2. {
  3. public void Configure(IApplicationBuilder app)
  4. {
  5. System.Console.WriteLine("MyPipeline");
  6. }
  7. }
  8. [MiddlewareFilter(typeof(MyPipeline))]
  9. public class HomeController : Controller { }

其他

IOrderedFilter

针对同一类型的过滤器,我们可以有多个实现,这些实现,可以注册到不同的作用域中,而且同一个作用域可以有多个该过滤器类型的实现。如果我们将这样的多个实现作用于同一个Action,这些过滤器实例的执行顺序就是我们所要关心的了。

默认的,如果将同一作用域的同一类型的过滤器的多个实现作用到某个Action上,则这些过滤器实例的执行顺序是按照注册的顺序进行的。

例如,我们现在有两个操作过滤器——MyActionFilter1和MyActionFilter2:

  1. public class MyActionFilter1 : ActionFilterAttribute
  2. {
  3. public override void OnActionExecuting(ActionExecutingContext context)
  4. {
  5. Console.WriteLine("OnActionExecuting: MyActionFilter1");
  6. }
  7. public override void OnResultExecuted(ResultExecutedContext context)
  8. {
  9. Console.WriteLine("OnResultExecuted: MyActionFilter1");
  10. }
  11. }
  12. public class MyActionFilter2 : ActionFilterAttribute
  13. {
  14. public override void OnActionExecuting(ActionExecutingContext context)
  15. {
  16. Console.WriteLine("OnActionExecuting: MyActionFilter2");
  17. }
  18. public override void OnResultExecuted(ResultExecutedContext context)
  19. {
  20. Console.WriteLine("OnResultExecuted: MyActionFilter2");
  21. }
  22. }

然后将其作用到HomeController.Index方法上,并且,先注册MyActionFilter2,再注册MyActionFilter1:

  1. public class HomeController : Controller
  2. {
  3. [MyActionFilter2]
  4. [MyActionFilter1]
  5. public IActionResult Index()
  6. {
  7. return View();
  8. }
  9. }

当请求Home/Index时,控制台的输出如下:

  1. OnActionExecuting: MyActionFilter2
  2. OnActionExecuting: MyActionFilter1
  3. OnResultExecuted: MyActionFilter1
  4. OnResultExecuted: MyActionFilter2

但是,我们在开发过程中,很容易手滑将注册顺序弄错,这时我们就需要一个手动指定执行顺序的机制,这就用到了IOrderedFilter接口。

  1. public interface IOrderedFilter : IFilterMetadata
  2. {
  3. // 执行顺序
  4. int Order { get; }
  5. }

IOrderedFilter接口很简单,只有一个Order属性,表示执行顺序,默认值为0。Order值越小,则过滤器的Before方法越先执行,After方法越后执行。

下面我们改造一下MyActionFilter1和MyActionFilter2,让MyActionFilter1先执行:

  1. public class MyActionFilter1 : ActionFilterAttribute
  2. {
  3. public MyActionFilter1()
  4. {
  5. Order = -1;
  6. }
  7. public override void OnActionExecuting(ActionExecutingContext context)
  8. {
  9. Console.WriteLine("OnActionExecuting: MyActionFilter1");
  10. }
  11. public override void OnResultExecuted(ResultExecutedContext context)
  12. {
  13. Console.WriteLine("OnResultExecuted: MyActionFilter1");
  14. }
  15. }
  16. public class MyActionFilter2 : ActionFilterAttribute
  17. {
  18. public MyActionFilter2()
  19. {
  20. Order = 1;
  21. }
  22. public override void OnActionExecuting(ActionExecutingContext context)
  23. {
  24. Console.WriteLine("OnActionExecuting: MyActionFilter2");
  25. }
  26. public override void OnResultExecuted(ResultExecutedContext context)
  27. {
  28. Console.WriteLine("OnResultExecuted: MyActionFilter2");
  29. }
  30. }

此时,再次请求Home/Index,控制台的输出如下:

  1. OnActionExecuting: MyActionFilter1
  2. OnActionExecuting: MyActionFilter2
  3. OnResultExecuted: MyActionFilter2
  4. OnResultExecuted: MyActionFilter1

现在,我们看一下不同作用域的情况下,Order是否生效。将MyActionFilter2作用域提升到控制器上。

  1. [MyActionFilter2]
  2. public class HomeController : Controller
  3. {
  4. [MyActionFilter1]
  5. public IActionResult Index()
  6. {
  7. return View();
  8. }
  9. }

此时,再次请求Home/Index,控制台的输出如下:

  1. OnActionExecuting: MyActionFilter1
  2. OnActionExecuting: MyActionFilter2
  3. OnResultExecuted: MyActionFilter2
  4. OnResultExecuted: MyActionFilter1

哇,神奇的事情发生了,作用域为Action的MyActionFilter1竟然优先于作用域为Controller的MyActionFilter2执行。

实际上,Order会重写作用域,即先按Order对过滤器进行排序,然后再通过作用域消除并列问题。

另外,若要始终首先执行全局过滤器,则请将Order设置为int.MinValue

  1. public void ConfigureServices(IServiceCollection services)
  2. {
  3. services.AddControllersWithViews(options =>
  4. {
  5. options.Filters.Add<MyActionFilter2>(int.MinValue);
  6. });
  7. }

理解ASP.NET Core - 过滤器(Filters)的更多相关文章

  1. 理解ASP.NET Core - 错误处理(Handle Errors)

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或[点击此处查看全文目录](https://www.cnblogs.com/xiaoxiaotank/p/151852 ...

  2. 理解ASP.NET Core - 日志(Logging)

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 快速上手 添加日志提供程序 在文章主机(Host)中,讲到Host.CreateDefault ...

  3. 理解ASP.NET Core - 模型绑定&验证(Model Binding and Validation)

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 模型绑定 什么是模型绑定?简单说就是将HTTP请求参数绑定到程序方法入参上,该变量可以是简单类 ...

  4. 理解ASP.NET Core - 授权(Authorization)

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 之前,我们已经了解了ASP.NET Core中的身份认证,现在,我们来聊一下授权. 老规矩,示 ...

  5. 理解ASP.NET Core - 发送Http请求(HttpClient)

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 前言 在.NET中,我们有很多发送Http请求的手段,如HttpWebRequest.WebC ...

  6. 理解 ASP.NET Core: 处理管道

    理解 ASP.NET Core 处理管道 在 ASP.NET Core 的管道处理部分,实现思想已经不是传统的面向对象模式,而是切换到了函数式编程模式.这导致代码的逻辑大大简化,但是,对于熟悉面向对象 ...

  7. 理解ASP.NET Core - [01] Startup

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 准备工作:一份ASP.NET Core Web API应用程序 当我们来到一个陌生的环境,第一 ...

  8. 目录-理解ASP.NET Core

    <理解ASP.NET Core>基于.NET5进行整理,旨在帮助大家能够对ASP.NET Core框架有一个清晰的认识. 目录 [01] Startup [02] Middleware [ ...

  9. 理解ASP.NET Core - [02] Middleware

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 中间件 先借用微软官方文档的一张图: 可以看到,中间件实际上是一种配置在HTTP请求管道中,用 ...

随机推荐

  1. .NET下使用ufun函数取CAM操作的进给速度

    UF_PARAM_ask_subobj_ptr_value,这个函数在封装的时候,给了很大一个坑啊. NXOpen.UF.UFParam.AskSubobjPtrValue(ByVal param_t ...

  2. Python 做简单的登录系统

    案例 之 登录系统原创作品1 该随笔 仅插入部分代码:全部py文件源代码请从百度网盘自行下载! 链接:https://pan.baidu.com/s/1_sTcDvs5XEGDcnpoQEIrMg 提 ...

  3. HTTP标签

    系统的http状态码知识,我是在<图解http里学习的>. 状态码的职责是告知从服务器端返回的请求结果. 分类如下: 2XX --> 成功 200 OK(一般情况) 204 No C ...

  4. JVM:内存溢出OOM

    JVM:内存溢出OOM 本笔记是根据bilibili上 尚硅谷 的课程 Java大厂面试题第二季 而做的笔记 经典错误 JVM 中常见的两个 OOM 错误 StackoverflowError:栈溢出 ...

  5. Java:容器类线程不安全

    Java:容器类线程不安全 本笔记是根据bilibili上 尚硅谷 的课程 Java大厂面试题第二季 而做的笔记 1. Collection 线程不安全的举例 前言 1.当我们执行下面语句的时候,底层 ...

  6. [对对子队]会议记录5.14(Scrum Meeting1)

    今天已完成的工作 何瑞 ​ 工作内容:初步完成循环指令系统 ​ 相关issue:实现循环语句系统的逻辑 ​ 相关签入:feat:循环语句的指令编辑系统初步完成 吴昭邦 ​ 工作内容:将流水线系统和循环 ...

  7. C语言教你写个‘浪漫烟花‘---特别漂亮

    效果展示 动态图 总体框架 /***************************************** * 项目名称:浪漫烟花 * 项目描述:贴图 * 项目环境:vs2019 * 生成日期: ...

  8. https的加密解密过程

    前置知识 SSL是90年代Netscape弄出来的一套东西,为的是解决HTTP协议明文传输数据的问题.后来SSL慢慢成了事实上的标准,于是IETF就把SSL标准化了,名字叫做TLS,TLS 1.0其实 ...

  9. Luogu P2822 [NOIp2016提高组]组合数问题 | 数学、二维前缀和

    题目链接 思路:组合数就是杨辉三角,那么我们只要构造一个杨辉三角就行了.记得要取模,不然会爆.然后,再用二维前缀和统计各种情况下组合数是k的倍数的方案数.询问时直接O(1)输出即可. #include ...

  10. 经典200例-002 为项目添加DLL文件引用

    项目右击,添加引用,(或菜单栏选择"项目","添加引用"),COM选项卡 复制去Google翻译翻译结果