ASP.NET Core 中文文档 第四章 MVC(4.3)过滤器

ASP.NET MVC 过滤器 可在执行管道的前后特定阶段执行代码。过滤器可以配置为全局有效、仅对控制器有效或是仅对 Action 有效。

过滤器如何工作?

不同的过滤器类型会在执行管道的不同阶段运行,因此它们各自有一套适用场景。根据你实际要解决的问题以及在请求管道中执行的位置来选择创建不同的过滤器。运行于 MVC Action 调用管道内的过滤器有时被称为 过滤管道 ,当 MVC 选择要执行哪个 Action 后就会先执行该 Action 上的过滤器。

不同过滤器在管道的不同位置运行。像授权这样的过滤器只运行在管道的靠前位置,并且其后也不会跟随 action。其它过滤器(如 action 过滤器等)可以在管道的其它部分之前或之后执行,如下所示。

选择过滤器

授权过滤器 用于确定当前用户的请求是否合法。

资源过滤器 是授权之后第一个用来处理请求的过滤器,也是最后一个接触到请求的过滤器(因为之后就会离开过滤器管道)。在性能方面,资源过滤器在实现缓存或短路过滤器管道尤其有用。

Action 过滤器 包装了对单个 action 方法的调用,可以将参数传递给 action 并从中获得 action result。

异常过滤器 为 MVC 应用程序未处理异常应用全局策略。

结果过滤器 包装了单个 action result 的执行,当且仅当 action 方法成功执行完毕后方才运行。它们是理想的围绕视图执行或格式处理的逻辑(所在之处)。

实现

所有过滤器均可通过不同的接口定义来支持同步和异步实现。根据你所需执行的任务的不同来选择同步还是异步实现。从框架的角度来讲它们是可以互换的。

同步过滤器定义了 OnStageExecuting 方法和 OnStageExecuted 方法(当然也有例外)。OnStageExecuting 方法在具体事件管道阶段之前调用,而 OnStageExecuted 方法则在之后调用(比如当 Stage 是 Action 时,这两个方法便是 OnActionExecuting 和 OnActionExecuted,译者注)。

using FiltersSample.Helper;
using Microsoft.AspNetCore.Mvc.Filters; namespace FiltersSample.Filters
{
public class SampleActionFilter : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// do something before the action executes
} public void OnActionExecuted(ActionExecutedContext context)
{
// do something after the action executes
}
}
}

异步过滤器定义了一个 On\ Stage\ ExecutionAsync 方法,可以在具体管道阶段的前后运行。On\ Stage\ ExecutionAsync 方法被提供了一个 Stage\ ExecutionDelegate 委托,当调用时该委托会执行具体管道阶段的工作,然后等待其完成。

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters; namespace FiltersSample.Filters
{
public class SampleAsyncActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
// do something before the action executes
await next();
// do something after the action executes
}
}
}

注解

只能 实现一个过滤器接口,要么是同步版本的,要么是异步版本的,鱼和熊掌不可兼得 。如果你需要在接口中执行异步工作,那么就去实现异步接口。否则应该实现同步版本的接口。框架会首先检查是不是实现了异步接口,如果实现了异步接口,那么将调用它。不然则调用同步接口的方法。如果一个类中实现了两个接口,那么只有异步方法会被调用。最后,不管 action 是同步的还是异步的,过滤器的同步或是异步是独立于 action 的。

过滤器作用域

过滤器具有三种不同级别的 作用域 。你可以在特定的 action 上用特性(Attribute)的方式使用特定的过滤器;也可以在控制器上用特性的方式使用过滤器,这样就会将效果应用在控制器内所有的 action 上;或者注册一个全局过滤器,它将作用于整个 MVC 应用程序下的每一个 action。

StartupConfigureServices
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add(typeof(SampleActionFilter)); // by type
options.Filters.Add(new SampleGlobalActionFilter()); // an instance
}); services.AddScoped<AddHeaderFilterWithDi>();
}
filters.Add(new TypeFilterAttribute(typeof(MyFilter)))
OnResultExecuting
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersSample.Filters
{
public class AddHeaderAttribute : ResultFilterAttribute
{
private readonly string _name;
private readonly string _value; public AddHeaderAttribute(string name, string value)
{
_name = name;
_value = value;
} public override void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(
_name, new string[] { _value });
base.OnResultExecuting(context);
}
}
}

特性允许过滤器接收参数,如下例所示。可将此特性加诸控制器(Controller)或 Action 方法,并为其指定所需 HTTP 头的名称和值,并将该 HTTP 头加入响应中:

[AddHeader("Author", "Steve Smith @ardalis")]
public class SampleController : Controller
{
public IActionResult Index()
{
return Content("Examine the headers using developer tools.");
}
}
Index

以下几种过滤器接口可以自定义为相应特性的实现。

过滤器特性:

取消与短路

ResultShortCircuitingResourceFilter
using System;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters; namespace FiltersSample.Filters
{
public class ShortCircuitingResourceFilterAttribute : Attribute,
IResourceFilter
{
public void OnResourceExecuting(ResourceExecutingContext context)
{
context.Result = new ContentResult()
{
Content = "Resource unavailable - header should not be set"
};
} public void OnResourceExecuted(ResourceExecutedContext context)
{
}
}
}
ShortCircuitingResourceFilterAddHeaderSomeResourceShortCircuitingResourceFilterSomeResourceAddHeaderShortCircuitingResourceFilter
[AddHeader("Author", "Steve Smith @ardalis")]
public class SampleController : Controller
{
[ShortCircuitingResourceFilter]
public IActionResult SomeResource()
{
return Content("Successful access to resource - header should be set.");
}
配置过滤器
Startup.csServiceFilterAttributeTypeFilterAttribute

依赖注入

以特性形式实现的、直接添加到控制器(Controller)类或 Action 方法的过滤器,其构造函数不得由 依赖注入 (DI)提供依赖项。其原因在于特性所需的构造函数参数必须由使用处直接提供。这是特性原型机理的限制。

不过,如果过滤器需要从 DI 中获得依赖项,那么有几种办法可以实现,可在类(class)或 Action 方法使用:

TypeFilterServiceFilterServiceFilter
[ServiceFilter(typeof(AddHeaderFilterWithDi))]
public IActionResult Index()
{
return View();
}
ConfigureServicesServiceFilter
System.InvalidOperationException: No service for type
'FiltersSample.Filters.AddHeaderFilterWithDI' has been registered.
ConfigureServicesAddHeaderFilterWithDI
services.AddScoped<AddHeaderFilterWithDi>();
ServiceFilterAttributeIFilterFactoryIFilterServiceFilterAttributeIFilterFactoryCreateInstance
TypeFilterAttributeServiceFilterAttributeIFilterFactoryMicrosoft.Extensions.DependencyInjection.ObjectFactory
TypeFilterAttributeTypeFilterAttributeTypeFilterAttribute
[TypeFilter(typeof(AddHeaderAttribute),
Arguments = new object[] { "Author", "Steve Smith (@ardalis)" })]
public IActionResult Hi(string name)
{
return Content($"Hi {name}");
}
TypeFilterAttribute[TypeFilter(typeof(FilterType))]
public class SampleActionFilterAttribute : TypeFilterAttribute
{
public SampleActionFilterAttribute():base(typeof(SampleActionFilterImpl))
{
} private class SampleActionFilterImpl : IActionFilter
{
private readonly ILogger _logger;
public SampleActionFilterImpl(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
} public void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogInformation("Business action starting...");
// perform some business logic work } public void OnActionExecuted(ActionExecutedContext context)
{
// perform some business logic work
_logger.LogInformation("Business action completed.");
}
}
}
[SampleActionFilter][TypeFilter][ServiceFilter]

注解

应避免纯粹为记录日志而创建和使用过滤器,这是因为 内建的框架日志功能 应该已经提供了你所需的功能。如果你要把日志记录功能放入过滤器中,它应专注于业务领域或过滤器的具体行为,而不是 MVC Action 或框架事件。

IFilterFactoryIFilterIFilterFactoryIFilterIFilterFactoryCreateInstanceIFilter
IFilterFactory
public class AddHeaderWithFactoryAttribute : Attribute, IFilterFactory
{
// Implement IFilterFactory
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return new InternalAddHeaderFilter();
} private class InternalAddHeaderFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(
"Internal", new string[] { "Header Added" });
} public void OnResultExecuted(ResultExecutedContext context)
{
}
}

排序

过滤器可应用于 Action 方法、控制器(Controller,通过特性(attribute)的形式)或添加到全局过滤器集合中。其作用域通常还决定了其执行顺序。最靠近 Action 的过滤器首先执行;通常来讲通过重写行为而不是显式设置顺序来改变顺序。这有时被称为“俄罗斯套娃”,因为每一个作用范围都包裹了前一个作用范围,就像是 套娃 那般。

intOrderTypeFilterAttributeServiceFilterAttributeIOrderedFilterOrderOrder
ControllerOnActionExecutingOnActionExecutedOrder
  1. The Controller OnActionExecuting
  2. The Global filter OnActionExecuting
  3. The Class filter OnActionExecuting
  4. The Method filter OnActionExecuting
  5. The Method filter OnActionExecuted
  6. The Class filter OnActionExecuted
  7. The Global filter OnActionExecuted
  8. The Controller OnActionExecuted
IOrderedFilter
OrderOrder=-1
[MyFilter(Name = "Method Level Attribute", Order=-1)]
Order

新的排序可能是这样的:

  1. The Controller OnActionExecuting
  2. The Method filter OnActionExecuting
  3. The Global filter OnActionExecuting
  4. The Class filter OnActionExecuting
  5. The Class filter OnActionExecuted
  6. The Global filter OnActionExecuted
  7. The Method filter OnActionExecuted
  8. The Controller OnActionExecuted
ControllerIFilterIFilter
授权过滤器

授权过滤器 控制对 action 方法的访问,也是过滤器管道中第一个被执行的过滤器。它们只有一个前置阶段,不像其它大多数过滤器支持前置阶段方法和后置阶段方法。只有当你使用自己的授权框架时才需要定制授权过滤器。谨记勿在授权过滤器内抛出异常,这是因为所抛出的异常不会被处理(异常过滤器也不会处理它们)。此时记录该问题或寻求其它办法。

更多请访问 Authorization

资源过滤器
IResourceFilterIAsyncResourceFilterOnResourceExecutingOnResourceExecuted
ContentResult
public class NaiveCacheResourceFilterAttribute : Attribute,
IResourceFilter
{
private static readonly Dictionary<string, object> _cache
= new Dictionary<string, object>();
private string _cacheKey; public void OnResourceExecuting(ResourceExecutingContext context)
{
_cacheKey = context.HttpContext.Request.Path.ToString();
if (_cache.ContainsKey(_cacheKey))
{
var cachedValue = _cache[_cacheKey] as string;
if (cachedValue != null)
{
context.Result = new ContentResult()
{ Content = cachedValue };
}
}
} public void OnResourceExecuted(ResourceExecutedContext context)
{
if (!String.IsNullOrEmpty(_cacheKey) &&
!_cache.ContainsKey(_cacheKey))
{
var result = context.Result as ContentResult;
if (result != null)
{
_cache.Add(_cacheKey, result.Content);
}
}
}
}
OnResourceExecutingResultcontextOnResourceExecutedResult

如下所示,把这个过滤器用于类或方法之上:

[TypeFilter(typeof(NaiveCacheResourceFilterAttribute))]
public class CachedController : Controller
{
public IActionResult Index()
{
return Content("This content was generated at " + DateTime.Now);
}
}
Action 过滤器
IActionFilterIAsyncActionFilter
OnActionExecutingActionExecutingContext.ActionArgumentsActionExecutingContext.ControllerOnActionExecutingActionExecutingContext.ResultOnActionExecuting
OnActionExecutedActionExecutedContext.ResultActionExecutedContext.CanceledActionExecutedContext.ExceptionActionExecutedContext.ExceptionActionExectedContext.Result
IAsyncActionFilterOnActionExecutionAsyncOnActionExecutingOnActionExecutedawait next()ActionExecutionDelegateActionExecutedContextOnActionExecutionAsyncActionExecutingContext.ResultActionExecutionDelegate
异常过滤器
IExceptionFilterIAsyncExceptionFilter

异常过滤器用于处理「未处理异常」,包括发生在 Controller 创建及 模型绑定 期间出现的异常。它们只在管道内发生异常时才会被调用。它们提供了一个单一的位置实现应用程序内的公共异常处理策略。框架提供了抽象的 ExceptionFilterAttribute ,你根据自己的需要继承这个类。异常过滤器适用于捕获 MVC Action 内出现的异常,但它们不及错误处理中间件(error handling middleware)灵活。一般来讲优先使用中间件,只有在需要做一些基于所选 MVC Action 的、有别于错误处理的工作时才选择使用过滤器。

提示

对于应用程序中不同 action 需要使用不同的错误处理方式,并向 Views/HTML 暴露 API 端点或 action 的错误处理结果。API 端点用 JSON 返回错误信息,而基于视图的 action 则返回错误页面(HTML 页面)。

OnExceptionOnExceptionAsyncOnExceptionExceptionContextExceptioncontext.Exception
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ViewFeatures; namespace FiltersSample.Filters
{
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IModelMetadataProvider _modelMetadataProvider; public CustomExceptionFilterAttribute(
IHostingEnvironment hostingEnvironment,
IModelMetadataProvider modelMetadataProvider)
{
_hostingEnvironment = hostingEnvironment;
_modelMetadataProvider = modelMetadataProvider;
} public override void OnException(ExceptionContext context)
{
if (!_hostingEnvironment.IsDevelopment())
{
// do nothing
return;
}
var result = new ViewResult {ViewName = "CustomError"};
result.ViewData = new ViewDataDictionary(_modelMetadataProvider,context.ModelState);
result.ViewData.Add("Exception", context.Exception);
// TODO: Pass additional detailed data via ViewData
context.ExceptionHandled = true; // mark exception as handled
context.Result = result;
}
}
}
结果过滤器
IResultFilterIAsyncResultFilterException = null
ViewResult

结果过滤器适用于任何需要直接环绕 View 或格式化处理的逻辑。结果过滤器可以替换或更改 Action 结果(而后者负责产生响应)。

OnResultExecutingResultExecutingContext.ResultResultExecutingContext.CancelOnResultExecutingOnResultExecuting
OnResultExecutedResultExecutedContext.CanceledResultExecutedContext.ExceptionResultExecutedContext.Exception
IAsyncResultFilterOnResultExecutionAsyncOnResultExecutingOnResultExecutedResultExecutionDelegateawait next()ResultExecutedContextResultExecutingContext.CancelResultExectionDelegateOnResultExecutionAsync
ResultFilterAttributeAddHeaderAttribute
OnResultExecutingOnResultExecuted
过滤器对比中间件

一般情况下,过滤器用于处理业务与应用程序的横切关注点。它的用法很像 中间件 。从能力上来讲过滤器酷似中间件,但过滤器的作用范围很大,因此允许你将它插入到应用程序中需要使用到它的场合中,比如在视图之前或在模型绑定之后。过滤器是 MVC 的一部分,可以访问 MVC 的上下文以及构造函数。比方说,中间件不能简单地直接察觉请求中模型验证是否生成了错误并对此作出响应,而过滤器却能做到。

如果想要尝试一下过滤器,可以下载、测试并修改样例 。