1. Filter 介紹
Filter 的作用是在 Action 執行前或執行後做一些加工處理。
某種程度來看,會跟 Middleware 很像,但執行的順序略有不同,用對 Filter 不僅可以減少程式碼,還可以減省執行效率。
ASP.NET Core 有以下五種 Filter 可以使用:
- Authorization Filter
Authorization 是五種 Filter 中優先序最高的,通常用於驗證 Request 合不合法,不合法後面就直接跳過。 - Resource Filter
Resource 是第二優先,會在 Authorization 之後,Model Binding 之前執行。通常會是需要對 Model 加工處裡才用。 - Action Filter
最容易使用的 Filter,封包進出都會經過它,使用上沒捨麼需要特別注意的。跟 Resource Filter 很類似,但並不會經過 Model Binding。 - Exception Filter
異常處理的 Exception。 - Result Filter
當 Action 完成後,最終會經過的 Filter。
2. Filter 運作方式
ASP.NET Core 的每個 Request 都會先經過已註冊的 Middleware 接著才會執行 Filter,除了會依照上述的順序外,同類型的 Filter 都會以先進後出的方式處裡封包。
Response 在某些 Filter 並不會做處理,會值接 Bypass。Request 及 Response 的運作流程如下圖:
黃色箭頭是正常情況流程
灰色箭頭是異常處理流程
3. 建立 Filter
ASP.NET Core 的 Filter 基本上跟 ASP.NET MVC 的差不多。
上述的五種 Filter 範例分別如下:
3.1. Authorization Filter
AuthorizationFilter.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 | using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Filters; namespace MyWebsite.Filters { public class AuthorizationFilter : IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n"); } } } |
非同步的方式:
1 2 3 4 5 6 7 8 | // ... public class AuthorizationFilter : IAsyncAuthorizationFilter { public async Task OnAuthorizationAsync(AuthorizationFilterContext context) { await context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n"); } } |
3.2. Resource Filter
ResourceFilter.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Filters; namespace MyWebsite.Filters { public class ResourceFilter : IResourceFilter { public void OnResourceExecuting(ResourceExecutingContext context) { context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n"); } public void OnResourceExecuted(ResourceExecutedContext context) { context.HttpContext.Response.WriteAsync($"{GetType().Name} out. \r\n"); } } } |
非同步的方式:
1 2 3 4 5 6 7 8 9 10 11 12 | // ... public class ResourceFilter : IAsyncResourceFilter { public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) { await context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n"); await next(); await context.HttpContext.Response.WriteAsync($"{GetType().Name} out. \r\n"); } } |
3.3. Action Filter
ActionFilter.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Filters; namespace MyWebsite.Filters { public class ActionFilter : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n"); } public void OnActionExecuted(ActionExecutedContext context) { context.HttpContext.Response.WriteAsync($"{GetType().Name} out. \r\n"); } } } |
非同步的方式:
1 2 3 4 5 6 7 8 9 10 11 12 | // ... public class ActionFilter : IAsyncActionFilter { public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { await context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n"); await next(); await context.HttpContext.Response.WriteAsync($"{GetType().Name} out. \r\n"); } } |
3.4. Result Filter
ResultFilter.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Filters; namespace MyWebsite.Filters { public class ResultFilter : IResultFilter { public void OnResultExecuting(ResultExecutingContext context) { context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n"); } public void OnResultExecuted(ResultExecutedContext context) { context.HttpContext.Response.WriteAsync($"{GetType().Name} out. \r\n"); } } } |
非同步的方式:
1 2 3 4 5 6 7 8 9 10 11 12 | // ... public class ResultFilter : IAsyncResultFilter { public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) { await context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n"); await next(); await context.HttpContext.Response.WriteAsync($"{GetType().Name} out. \r\n"); } } |
3.5. Exception Filter
ExceptionFilter.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 | using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Filters; namespace MyWebsite.Filters { public class ExceptionFilter : IExceptionFilter { public void OnException(ExceptionContext context) { context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n"); } } } |
非同步的方式:
1 2 3 4 5 6 7 8 9 | // ... public class ExceptionFilter : IAsyncExceptionFilter { public Task OnExceptionAsync(ExceptionContext context) { context.HttpContext.Response.WriteAsync($"{GetType().Name} in. \r\n"); return Task.CompletedTask; } } |
4. 註冊 Filter
Filter 有兩種註冊方式,第一種是全域註冊,另一種是用 Attribute 區域註冊的方式,只套用在特定的 Action。
4.1. 全域註冊
在 Startup.cs 註冊 Filter,這樣就可以套用到所有的 Request。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | // ... public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddMvc(config => { config.Filters.Add(new ResultFilter()); config.Filters.Add(new ExceptionFilter()); config.Filters.Add(new ResourceFilter()); }); } } |
4.2. 區域註冊
ASP.NET Core 在 Attribute 註冊 Filter 的方式跟 ASP.NET MVC 有一點不一樣,要透過 [TypeFilter(type)]
。
在 Controller 或 Action 上面加上 [TypeFilter(type)]
就可以區域註冊 Filter。如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | // ... namespace MyWebsite.Controllers { [TypeFilter(typeof(AuthorizationFilter))] public class HomeController : Controller { [TypeFilter(typeof(ActionFilter))] public void Index() { Response.WriteAsync("Hello World! \r\n"); } [TypeFilter(typeof(ActionFilter))] public void Error() { throw new System.Exception("Error"); } } } |
執行結果
程式碼下載
參考
=======================================================================================
ASP.NET Core 中的筛选器
通过使用 ASP.NET Core MVC 中的筛选器,可在请求处理管道中的特定阶段之前或之后运行代码。
内置筛选器处理任务,例如:
- 授权(防止用户访问未获授权的资源)。
- 确保所有请求都使用 HTTPS。
- 响应缓存(对请求管道进行短路出路,以便返回缓存的响应)。
可以创建自定义筛选器,用于处理横切关注点。 筛选器可以避免跨操作复制代码。 例如,错误处理异常筛选器可以合并错误处理。
筛选器的工作原理
筛选器在 MVC 操作调用管道(有时称为筛选器管道)内运行。 筛选器管道在 MVC 选择了要执行的操作之后运行。
筛选器类型
每种筛选器类型都在筛选器管道中的不同阶段执行。
-
授权筛选器最先运行,用于确定是否已针对当前请求为当前用户授权。 如果请求未获授权,它们可以让管道短路。
-
资源筛选器是授权后最先处理请求的筛选器。 它们可以在筛选器管道的其余阶段运行之前以及管道的其余阶段完成之后运行代码。 出于性能方面的考虑,可以使用它们来实现缓存或以其他方式让筛选器管道短路。 它们在模型绑定之前运行,所以可以影响模型绑定。
-
操作筛选器可以在调用单个操作方法之前和之后立即运行代码。 它们可用于处理传入某个操作的参数以及从该操作返回的结果。 不可在 Razor Pages 中使用操作筛选器。
-
异常筛选器用于在向响应正文写入任何内容之前,对未经处理的异常应用全局策略。
-
结果筛选器可以在执行单个操作结果之前和之后立即运行代码。 仅当操作方法成功执行时,它们才会运行。 对于必须围绕视图或格式化程序的执行的逻辑,它们很有用。
下图展示了这些筛选器类型在筛选器管道中的交互方式。
实现
通过不同的接口定义,筛选器同时支持同步和异步实现。
可在其管道阶段之前和之后运行代码的同步筛选器定义 OnStageExecuting 方法和 OnStageExecuted 方法。 例如,在调用操作方法之前调用 OnActionExecuting
,在操作方法返回之后调用 OnActionExecuted
。
C#复制
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
}
}
}
异步筛选器定义单一的 OnStageExecutionAsync 方法。 此方法采用 FilterTypeExecutionDelegate 委托来执行筛选器的管道阶段。 例如,ActionExecutionDelegate
调用该操作方法或下一个操作筛选器,用户可以在调用它之前和之后执行代码。
C#复制
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
var resultContext = await next();
// do something after the action executes; resultContext.Result will be set
}
}
}
可以在单个类中为多个筛选器阶段实现接口。 例如,ActionFilterAttribute 类实现 IActionFilter
、IResultFilter
及其异步等效接口。
备注
筛选器接口的同步和异步版本任意实现一个,而不是同时实现。 该框架会先查看筛选器是否实现了异步接口,如果是,则调用该接口。 如果不是,则调用同步接口的方法。 如果在一个类中同时实现了这两种接口,则仅调用异步方法。 使用抽象类时(如 ActionFilterAttribute),将为每种筛选器类型仅重写同步方法或仅重写异步方法。
IFilterFactory
IFilterFactory 实现 IFilterMetadata。 因此,IFilterFactory
实例可在筛选器管道中的任意位置用作 IFilterMetadata
实例。 当该框架准备调用筛选器时,它会尝试将其转换为 IFilterFactory
。 如果强制转换成功,则调用 CreateInstance 方法来创建将调用的 IFilterMetadata
实例。 这提供了一种很灵活的设计,因为无需在应用启动时显式设置精确的筛选器管道。
用户可以在自己的属性实现上实现 IFilterFactory
作为另一种创建筛选器的方法:
C#复制
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)
{
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
内置筛选器属性
该框架包含许多可子类化和自定义的基于属性的内置筛选器。 例如,以下结果筛选器会向响应添加标头。
C#复制
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);
}
}
}
通过使用属性,筛选器可接收参数,如上面的示例所示。 可将此属性添加到控制器或操作方法,并指定 HTTP 标头的名称和值:
C#复制
[AddHeader("Author", "Steve Smith @ardalis")]
public class SampleController : Controller
{
public IActionResult Index()
{
return Content("Examine the headers using developer tools.");
}
[ShortCircuitingResourceFilter]
public IActionResult SomeResource()
{
return Content("Successful access to resource - header should be set.");
}
Index
操作的结果如下所示:响应标头显示在右下角。
多种筛选器接口具有相应属性,这些属性可用作自定义实现的基类。
筛选器属性:
ActionFilterAttribute
ExceptionFilterAttribute
ResultFilterAttribute
FormatFilterAttribute
ServiceFilterAttribute
TypeFilterAttribute
本文稍后会对 TypeFilterAttribute
和 ServiceFilterAttribute
进行介绍。
筛选器作用域和执行顺序
可以将筛选器添加到管道中的三个作用域之一。 可以使用属性将筛选器添加到特定的操作方法或控制器类。 或者,也可以注册所有控制器和操作的全局筛选器。 通过将筛选器添加到 ConfigureServices
中的 MvcOptions.Filters
集合,可以将其添加为全局筛选器:
C#复制
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader",
"Result filter added to MvcOptions.Filters")); // an instance
options.Filters.Add(typeof(SampleActionFilter)); // by type
options.Filters.Add(new SampleGlobalActionFilter()); // an instance
});
services.AddScoped<AddHeaderFilterWithDi>();
}
默认执行顺序
当管道的某个特定阶段有多个筛选器时,作用域可确定筛选器执行的默认顺序。 全局筛选器涵盖类筛选器,类筛选器又涵盖方法筛选器。 这种模式有时称为“俄罗斯套娃”嵌套,因为增加的每个作用域都包装在前一个作用域中,就像套娃一样。 通常情况下,无需显式确定排序便可获得所需的重写行为。
在这种嵌套模式下,筛选器的 after 代码会按照与 before 代码相反的顺序运行。 其序列如下所示:
- 筛选器的 before 代码应用于全局
- 筛选器的 before 代码应用于控制器
- 筛选器的 before 代码应用于操作方法
- 筛选器的 after 代码应用于操作方法
- 筛选器的 after 代码应用于控制器
- 筛选器的 before 代码应用于控制器
- 筛选器的 after 代码应用于全局
下面的示例阐释了为同步操作筛选器调用筛选器方法的顺序。
序列 | 筛选器作用域 | 筛选器方法 |
---|---|---|
1 | Global | OnActionExecuting |
2 | 控制器 | OnActionExecuting |
3 | 方法 | OnActionExecuting |
4 | 方法 | OnActionExecuted |
5 | 控制器 | OnActionExecuted |
6 | Global | OnActionExecuted |
此序列显示:
- 方法筛选器已嵌套在控制器筛选器中。
- 控制器筛选器已嵌套在全局筛选器中。
换句话说,如果处于异步筛选器的 OnStageExecutionAsync 方法内,则当代码位于堆栈上时,所有筛选器都在更严格的作用域中运行。
备注
继承自 Controller
基类的每个控制器都包括 OnActionExecuting
和 OnActionExecuted
方法。 这些方法包装针对某项给定操作运行的筛选器:OnActionExecuting
在所有筛选器之前调用,OnActionExecuted
在所有筛选器之后调用。
重写默认顺序
可以通过实现 IOrderedFilter
来重写默认执行序列。 此接口公开了一个 Order
属性来确定执行顺序,该属性优先于作用域。 具有较低 Order
值的筛选器会在具有较高 Order
值的筛选器之前执行其 before 代码。 具有较低 Order
值的筛选器会在具有较高 Order
值的筛选器之后执行其 after 代码。 可使用构造函数参数来设置 Order
属性:
C#复制
[MyFilter(Name = "Controller Level Attribute", Order=1)]
如果具有上述示例中所示的 3 个相同的操作筛选器,但将控制器和全局筛选器的 Order
属性分别设置为 1 和 2,则会反转执行顺序。
序列 | 筛选器作用域 | Order 属性 | 筛选器方法 |
---|---|---|---|
1 | 方法 | 0 | OnActionExecuting |
2 | 控制器 | 1 | OnActionExecuting |
3 | Global | 2 | OnActionExecuting |
4 | Global | 2 | OnActionExecuted |
5 | 控制器 | 1 | OnActionExecuted |
6 | 方法 | 0 | OnActionExecuted |
在确定筛选器的运行顺序时,Order
属性优先于作用域。 先按顺序对筛选器排序,然后使用作用域消除并列问题。 所有内置筛选器实现 IOrderedFilter
并将默认 Order
值设为 0。 对于内置筛选器,作用域会确定顺序,除非将 Order
设为非零值。
取消和设置短路
通过设置提供给筛选器方法的 context
参数上的 Result
属性,可以在筛选器管道的任意位置设置短路。 例如,以下资源筛选器将阻止执行管道的其余阶段。
C#复制
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)
{
}
}
}
在下面的代码中,ShortCircuitingResourceFilter
和 AddHeader
筛选器都以 SomeResource
操作方法为目标。 ShortCircuitingResourceFilter
:
- 先运行,因为它是资源筛选器且
AddHeader
是操作筛选器。 - 对管道的其余部分进行短路处理。
这样 AddHeader
筛选器就不会为 SomeResource
操作运行。 如果这两个筛选器都应用于操作方法级别,只要 ShortCircuitingResourceFilter
先运行,此行为就不会变。 先运行 ShortCircuitingResourceFilter
(考虑到它的筛选器类型),或显式使用 Order
属性。
C#复制
[AddHeader("Author", "Steve Smith @ardalis")]
public class SampleController : Controller
{
public IActionResult Index()
{
return Content("Examine the headers using developer tools.");
}
[ShortCircuitingResourceFilter]
public IActionResult SomeResource()
{
return Content("Successful access to resource - header should be set.");
}
依赖关系注入
可按类型或实例添加筛选器。 如果添加实例,该实例将用于每个请求。 如果添加类型,则将激活该类型,这意味着将为每个请求创建一个实例,并且依赖关系注入 (DI) 将填充所有构造函数依赖项。 按类型添加筛选器等效于 filters.Add(new TypeFilterAttribute(typeof(MyFilter)))
。
如果将筛选器作为属性实现并直接添加到控制器类或操作方法中,则该筛选器不能由依赖关系注入 (DI) 提供构造函数依赖项。 这是因为属性在应用时必须提供自己的构造函数参数。 这是属性工作原理上的限制。
如果筛选器具有一些需要从 DI 访问的依赖项,有几种受支持的方法可用。 可以使用以下接口之一,将筛选器应用于类或操作方法:
ServiceFilterAttribute
TypeFilterAttribute
- 在属性上实现的
IFilterFactory
备注
记录器就是一种可能需要从 DI 获取的依赖项。 但是,应避免单纯为进行日志记录而创建和使用筛选器,因为内置的框架日志记录功能可能已经提供用户所需。 如果要将日志记录功能添加到筛选器,它应重点关注业务领域问题或特定于筛选器的行为,而非 MVC 操作或其他框架事件。
ServiceFilterAttribute
在 DI 中注册服务筛选器实现类型。 ServiceFilterAttribute
可从 DI 检索筛选器实例。 将 ServiceFilterAttribute
添加到 Startup.ConfigureServices
中的容器中,并在 [ServiceFilter]
属性中引用它:
C#复制
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc(options =>
{
options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader",
"Result filter added to MvcOptions.Filters")); // an instance
options.Filters.Add(typeof(SampleActionFilter)); // by type
options.Filters.Add(new SampleGlobalActionFilter()); // an instance
});
services.AddScoped<AddHeaderFilterWithDi>();
}
C#复制
[ServiceFilter(typeof(AddHeaderFilterWithDi))]
public IActionResult Index()
{
return View();
}
使用 ServiceFilterAttribute
时,IsReusable
设置会提示:筛选器实例可能在其创建的请求范围之外被重用。 该框架不保证在稍后的某个时刻将创建筛选器的单个实例,或不会从 DI 容器重新请求筛选器。 如果使用的筛选器依赖于具有除单一实例以外的生命周期的服务,请避免使用 IsReusable
。
使用 ServiceFilterAttribute
时不注册筛选器类型会引发异常:
复制
System.InvalidOperationException: No service for type
'FiltersSample.Filters.AddHeaderFilterWithDI' has been registered.
ServiceFilterAttribute
可实现 IFilterFactory
。 IFilterFactory
公开用于创建 IFilterMetadata
实例的 CreateInstance
方法。 CreateInstance
方法从服务容器 (DI) 中加载指定的类型。
TypeFilterAttribute
TypeFilterAttribute
与 ServiceFilterAttribute
类似,但不会直接从 DI 容器解析其类型。 它使用 Microsoft.Extensions.DependencyInjection.ObjectFactory
对类型进行实例化。
由于存在这种差异,所以存在以下情况:
- 使用
TypeFilterAttribute
引用的类型不需要先注册在容器中。 它们具备由容器实现的依赖项。 TypeFilterAttribute
可以选择为类型接受构造函数参数。
使用 TypeFilterAttribute
时,IsReusable
设置会提示:筛选器实例可能在其创建的请求范围之外被重用。 该框架不保证将创建筛选器的单一实例。 如果使用的筛选器依赖于具有除单一实例以外的生命周期的服务,请避免使用 IsReusable
。
下面的示例演示如何使用 TypeFilterAttribute
将参数传递到类型:
C#复制
[TypeFilter(typeof(LogConstantFilter),
Arguments = new object[] { "Method 'Hi' called" })]
public IActionResult Hi(string name)
{
return Content($"Hi {name}");
}
C#复制
public class LogConstantFilter : IActionFilter
{
private readonly string _value;
private readonly ILogger<LogConstantFilter> _logger;
public LogConstantFilter(string value, ILogger<LogConstantFilter> logger)
{
_logger = logger;
_value = value;
}
public void OnActionExecuting(ActionExecutingContext context)
{
_logger.LogInformation(_value);
}
public void OnActionExecuted(ActionExecutedContext context)
{ }
}
在属性上实现 IFilterFactory
如果你的筛选器符合以下描述:
- 不需要任何参数。
- 具备需要由 DI 填充的构造函数依赖项。
在类和方法上可以不使用 [TypeFilter(typeof(FilterType))]
改用自己命名的属性。 下面的筛选器展示了如何实现此操作:
C#复制
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]
。
授权筛选器
授权筛选器:
- 控制对操作方法的访问。
- 是筛选器管道中要执行的第一个筛选器。
- 具有在它之前的执行的方法,但没有之后执行的方法。
用户只有在编写自己的授权框架时,才应编写自定义授权筛选器。 建议配置授权策略或编写自定义授权策略,而不是编写自定义筛选器。 内置筛选器实现只负责调用授权系统。
切勿在授权筛选器内引发异常,因为没有任何能处理该异常的组件(异常筛选器不会进行处理)。 在出现异常时请小心应对。
详细了解授权。
资源筛选器
- 实现
IResourceFilter
或IAsyncResourceFilter
接口, - 它们的执行会覆盖筛选器管道的绝大部分。
- 只有授权筛选器在资源筛选器之前运行。
如果需要使某个请求正在执行的大部分工作短路,资源筛选器会很有用。 例如,如果响应在缓存中,则缓存筛选器可以绕开管道的其余阶段。
前面所示的短路资源筛选器便是一种资源筛选器。 另一个示例是 DisableFormValueModelBindingAttribute:
- 可以防止模型绑定访问表单数据。
- 如果要上传大型文件,同时想防止表单被读入内存,那么此筛选器会很有用。
操作筛选器
重要
操作筛选器不应用于 Razor Pages。 Razor Pages 支持 IPageFilter 和 IAsyncPageFilter。 有关详细信息,请参阅 Razor 页面的筛选方法。
操作筛选器:
- 实现
IActionFilter
或IAsyncActionFilter
接口。 - 它们的执行围绕着操作方法的执行。
下面是一个操作筛选器示例:
C#复制
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
}
}
ActionExecutingContext 提供以下属性:
ActionArguments
:用于处理对操作的输入。Controller
:用于处理控制器实例。Result
:设置此属性会使操作方法和后续操作筛选器的执行短路。 引发异常也会阻止操作方法和后续筛选器的执行,但会被视为失败,而不是一个成功的结果。
ActionExecutedContext 提供 Controller
和 Result
以及以下属性:
Canceled
:如果操作执行已被另一个筛选器设置短路,则为 true。Exception
:如果操作或后续操作筛选器引发了异常,则为非 NULL 值。 将此属性设置为 NULL 可有效地“处理”异常,并且将执行Result
,就像它是从操作方法正常返回的一样。
对于 IAsyncActionFilter
,一个向 ActionExecutionDelegate
的调用可以达到以下目的:
- 执行所有后续操作筛选器和操作方法。
- 返回
ActionExecutedContext
。
若要设置短路,可将 ActionExecutingContext.Result
分配到某个结果实例,并且不调用 ActionExecutionDelegate
。
该框架提供一个可子类化的抽象 ActionFilterAttribute
。
操作筛选器可用于验证模型状态,并在状态为无效时返回任何错误:
C#复制
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace FiltersSample.Filters
{
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(context.ModelState);
}
}
}
}
OnActionExecuted
方法在操作方法之后运行,可通过 ActionExecutedContext.Result
属性查看和处理操作结果。 如果操作执行已被另一个筛选器设置短路,则 ActionExecutedContext.Canceled
设置为 true。 如果操作或后续操作筛选器引发了异常,则 ActionExecutedContext.Exception
设置为非 NULL 值。 将 ActionExecutedContext.Exception
设置为 null:
- 有效地“处理”异常。
- 执行
ActionExectedContext.Result
,从操作方法中将它正常返回。
异常筛选器
异常筛选器可实现 IExceptionFilter
或 IAsyncExceptionFilter
接口。 它们可用于为应用实现常见的错误处理策略。
下面的异常筛选器示例使用自定义开发人员错误视图,显示在开发应用时发生的异常的相关详细信息:
C#复制
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.Result = result;
}
}
异常筛选器:
- 没有之前和之后的事件。
- 实现
OnException
或OnExceptionAsync
。 - 处理控制器创建、模型绑定、操作筛选器或操作方法中发生的未经处理的异常。
- 请不要捕获资源筛选器、结果筛选器或 MVC 结果执行中发生的异常。
若要处理异常,请将 ExceptionContext.ExceptionHandled
属性设置为 true,或编写响应。 这将停止传播异常。 异常筛选器无法将异常转变为“成功”。 只有操作筛选器才能执行该转变。
备注
在 ASP.NET Core 1.1 中,如果将 ExceptionHandled
设置为 true 并编写响应,则不会发送响应。 在这种情况下,ASP.NET Core 1.0 不发送响应,ASP.NET Core 1.1.2 则恢复为 1.0 的行为。 有关详细信息,请参阅 GitHub 存储库中的问题编号 5594。
异常筛选器:
- 非常适合捕获发生在 MVC 操作中的异常。
- 并不像错误处理中间件那么灵活。
建议使用中间件处理异常。 仅在需要根据所选 MVC 操作以不同方式执行错误处理时,才使用异常筛选器。 例如,应用可能具有用于 API 终结点和视图/HTML 的操作方法。 API 终结点可能返回 JSON 形式的错误信息,而基于视图的操作可能返回 HTML 形式的错误页。
ExceptionFilterAttribute
可以子类化。
结果筛选器
- 实现接口:
IResultFilter
或IAsyncResultFilter
。IAlwaysRunResultFilter
或IAsyncAlwaysRunResultFilter
- 它们的执行围绕着操作结果的执行。
IResultFilter 和 IAsyncResultFilter
下面是一个添加 HTTP 标头的结果筛选器示例。
C#复制
public class AddHeaderFilterWithDi : IResultFilter
{
private ILogger _logger;
public AddHeaderFilterWithDi(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<AddHeaderFilterWithDi>();
}
public void OnResultExecuting(ResultExecutingContext context)
{
var headerName = "OnResultExecuting";
context.HttpContext.Response.Headers.Add(
headerName, new string[] { "ResultExecutingSuccessfully" });
_logger.LogInformation($"Header added: {headerName}");
}
public void OnResultExecuted(ResultExecutedContext context)
{
// Can't add to headers here because response has already begun.
}
}
要执行的结果类型取决于所执行的操作。 返回视图的 MVC 操作会将所有 Razor 处理作为要执行的 ViewResult
的一部分。 API 方法可能会将某些序列化操作作为结果执行的一部分。 详细了解操作结果
当操作或操作筛选器生成操作结果时,仅针对成功的结果执行结果筛选器。 当异常筛选器处理异常时,不执行结果筛选器。
OnResultExecuting
方法可以将 ResultExecutingContext.Cancel
设置为 true,使操作结果和后续结果筛选器的执行短路。 设置短路时,通常应写入响应对象,以免生成空响应。 如果引发异常,则会导致:
- 阻止操作结果和后续筛选器的执行。
- 结果被视为失败而不是成功。
当 OnResultExecuted
方法运行时,响应可能已发送到客户端,而且不能再更改(除非引发了异常)。 如果操作结果执行已被另一个筛选器设置短路,则 ResultExecutedContext.Canceled
设置为 true。
如果操作结果或后续结果筛选器引发了异常,则 ResultExecutedContext.Exception
设置为非 NULL 值。 将 Exception
设置为 NULL 可有效地“处理”异常,并防止 MVC 在管道的后续阶段重新引发该异常。 在处理结果筛选器中的异常时,可能无法向响应写入任何数据。 如果操作结果在其执行过程中引发异常,并且标头已刷新到客户端,则没有任何可靠的机制可用于发送失败代码。
对于 IAsyncResultFilter
,通过调用 ResultExecutionDelegate
上的 await next
可执行所有后续结果筛选器和操作结果。 若要设置短路,可将 ResultExecutingContext.Cancel
设置为 true,并且不调用 ResultExectionDelegate
。
该框架提供一个可子类化的抽象 ResultFilterAttribute
。 前面所示的 AddHeaderAttribute 类是一种结果筛选器属性。
IAlwaysRunResultFilter 和 IAsyncAlwaysRunResultFilter
IAlwaysRunResultFilter 和 IAsyncAlwaysRunResultFilter 接口声明了一个针对操作结果运行的 IResultFilter 实现。 除非应用 IExceptionFilter 或 IAuthorizationFilter 并使响应短路,否则会将筛选器应用于操作结果。
换句话说,这些“始终运行”的筛选器始终运行,除非异常或授权筛选器使它们短路。 除 IExceptionFilter
和 IAuthorizationFilter
之外的筛选器不会使它们短路。
例如,以下筛选器始终运行并在内容协商失败时设置具有“422 无法处理的实体”状态代码的操作结果 (ObjectResult):
C#复制
public class UnprocessableResultFilter : Attribute, IAlwaysRunResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is StatusCodeResult statusCodeResult &&
statusCodeResult.StatusCode == 415)
{
context.Result = new ObjectResult("Can't process this!")
{
StatusCode = 422,
};
}
}
public void OnResultExecuted(ResultExecutedContext context)
{
}
}
在筛选器管道中使用中间件
资源筛选器的工作方式与中间件类似,即涵盖管道中的所有后续执行。 但筛选器又不同于中间件,它们是 MVC 的一部分,这意味着它们有权访问 MVC 上下文和构造。
在 ASP.NET Core 1.1 中,可以在筛选器管道中使用中间件。 如果有一个中间件组件,该组件需要访问 MVC 路由数据,或者只能针对特定控制器或操作运行,则可能需要这样做。
若要将中间件用作筛选器,可创建一个具有 Configure
方法的类型,该方法可指定要注入到筛选器管道的中间件。 下面的示例使用本地化中间件为请求建立当前区域性:
C#复制
public class LocalizationPipeline
{
public void Configure(IApplicationBuilder applicationBuilder)
{
var supportedCultures = new[]
{
new CultureInfo("en-US"),
new CultureInfo("fr")
};
var options = new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture(culture: "en-US", uiCulture: "en-US"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
};
options.RequestCultureProviders = new[]
{ new RouteDataRequestCultureProvider() { Options = options } };
applicationBuilder.UseRequestLocalization(options);
}
}
然后,可以使用 MiddlewareFilterAttribute
为所选控制器或操作或者在全局范围内运行中间件:
C#复制
[Route("{culture}/[controller]/[action]")]
[MiddlewareFilter(typeof(LocalizationPipeline))]
public IActionResult CultureFromRouteData()
{
return Content($"CurrentCulture:{CultureInfo.CurrentCulture.Name},"
+ $"CurrentUICulture:{CultureInfo.CurrentUICulture.Name}");
}
中间件筛选器与资源筛选器在筛选器管道的相同阶段运行,即,在模型绑定之前以及管道的其余阶段之后。