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。
如果想要使用全局过滤器的话,在你配置 MVC 的时候在 Startup
的 ConfigureServices
方法中添加:
过滤器可通过类型添加,也可以通过实例添加。如果通过实例添加,则该实例会被用于每一个请求。如果通过类型添加,则将会 type-activated(意思是说每次请求都会创建一个实例,其所有构造函数依赖项都将通过 DI 来填充)。通过类型添加过滤器相当于 filters.Add(new TypeFilterAttribute(typeof(MyFilter)))
。
把过滤器接口的实现当做 特性(Attributes) 使用是极为方便的。过滤器特性(filter attributes)可应用于控制器(Controllers)和 Action 方法。框架包含了内置的基于特性的过滤器,你可继承它们或另外定制。比方说,下例过滤器继承了 ResultFilterAttribute ,并重写(override)了 OnResultExecuting
方法(在响应中增加了一个头信息)。
特性允许过滤器接收参数,如下例所示。可将此特性加诸控制器(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
Action 的结果如下所示:响应的头信息显示在右下角。
以下几种过滤器接口可以自定义为相应特性的实现。
过滤器特性:
- ActionFilterAttribute
- ExceptionFilterAttribute
- ResultFilterAttribute
- FormatFilterAttribute
- ServiceFilterAttribute
- TypeFilterAttribute
取消与短路
通过设置传入过滤器方法的上下文参数中的 Result
属性,可以在过滤器管道的任意一点短路管道。比方说,下面的 ShortCircuitingResourceFilter
将阻止所有它之后管道内的所有过滤器,包括所有 action 过滤器。
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
的 action 方法。然而,由于首先运行的是 ShortCircuitingResourceFilter
,它短路了剩下的管道,SomeResource
上的 AddHeader
过滤器不会运行。如果这两个过滤器都以 Action 方法级别出现,它们的结果也会是一样的(只要 ShortCircuitingResourceFilter
首先运行,请查看 Ordering )。
配置过滤器
全局过滤器在 Startup.cs
中配置。基于特性的过滤器如果不需要任何依赖项的话,可以简单地继承一个与已存在的过滤器相对应的特性类型。如果要创建一个 非 全局作用域、但需要从依赖注入(DI)中获得依赖项的过滤器,在它们上面加上 ServiceFilterAttribute
或 TypeFilterAttribute
特性,这样就可用于控制器或 action 了。
依赖注入
以特性形式实现的、直接添加到控制器(Controller)类或 Action 方法的过滤器,其构造函数不得由 依赖注入 (DI)提供依赖项。其原因在于特性所需的构造函数参数必须由使用处直接提供。这是特性原型机理的限制。
不过,如果过滤器需要从 DI 中获得依赖项,那么有几种办法可以实现,可在类(class)或 Action 方法使用:
- ServiceFilterAttribute
- TypeFilterAttribute
- IFilterFactory 实现你的特性
TypeFilter
将为其依赖项从 DI 中使用服务(services)来实例化一个实例。ServiceFilter
则从 DI 中取回一个过滤器实例。下例中将演示如何使用 ServiceFilter
:
[ServiceFilter(typeof(AddHeaderFilterWithDi))]
public IActionResult Index()
{
return View();
}
如果在 ConfigureServices
中直接使用未经注册的 ServiceFilter
过滤器,则会抛出以下异常:
System.InvalidOperationException: No service for type
'FiltersSample.Filters.AddHeaderFilterWithDI' has been registered.
为避免此异常,你必须在 ConfigureServices
中为 AddHeaderFilterWithDI
类型注册:
services.AddScoped<AddHeaderFilterWithDi>();
ServiceFilterAttribute
实现了 IFilterFactory
接口,后者暴露了创建 IFilter
实例的单一方法。在 ServiceFilterAttribute
中,接口 IFilterFactory
中定义的 CreateInstance
方法被实现为用于从服务容器(DI)加载指定类型。
TypeFilterAttribute
很像 ServiceFilterAttribute
(它同样是 IFilterFactory
的实现),但此类型并非直接解析自 DI 容器。
相反,它通过使用 Microsoft.Extensions.DependencyInjection.ObjectFactory
来实例化类型。
由于这种不同,使用 TypeFilterAttribute
引用的类型不需要在使用之前向容器注册(但它们依旧将由容器来填充其依赖项)。同样地,TypeFilterAttribute
能可选地接受该类型的构造函数参数。下例演示如何向使用 TypeFilterAttribute
修饰的类型传递参数:
[TypeFilter(typeof(AddHeaderAttribute),
Arguments = new object[] { "Author", "Steve Smith (@ardalis)" })]
public IActionResult Hi(string name)
{
return Content($"Hi {name}");
}
若是你有一个简单的不需要任何参数的、但其构造函数需要通过 DI 填充依赖项的过滤器的话,你可以通过继承 TypeFilterAttribute
,在类(class)或方法(method)上使用自己命名的特性(来取代 [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 或框架事件。
IFilterFactory
实现了 IFilter
。因此,在过滤器管道的任何地方 IFilterFactory
实例都可当做 IFilter
实例来使用。当框架准备调用过滤器,将试图把其强制转换为 IFilterFactory
。如果转换成功,将通过调用 CreateInstance
方法创建即将被调用的 IFilter
实例。因为过滤器管道不需要在应用程序启动时显式设置了,所以这是一种非常灵活的设计。
你可以在自己的特性实现中实现 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 的过滤器首先执行;通常来讲通过重写行为而不是显式设置顺序来改变顺序。这有时被称为“俄罗斯套娃”,因为每一个作用范围都包裹了前一个作用范围,就像是 套娃 那般。
除了作用范围,过滤器还可以通过实现 IOrderedFilter 来重写它们的执行顺序。该接口只是简单地暴露了 int
Order
属性,然后执行时根据该数字正排序(数字越小,越先执行)后依次执行过滤器。所有内建过滤器(包括 TypeFilterAttribute
和 ServiceFilterAttribute
)都实现了 IOrderedFilter
接口,因此当你将过滤器以特性的方式用于类(class)或方法(method)时你可以指定每一个过滤器的执行顺序。默认情况下所有内建过滤器的 Order
属性值都为 0,因此作用范围就当做决定性的因素(除非存在不为 0 的 Order
值)。
每个继承自 Controller
基类的控制器(Controller)都包含 OnActionExecuting
和 OnActionExecuted
方法。这些方法为给定的 Action 包装了过滤器,它们分别在最先和最后运行。基于作用范围的顺序(假设没有为过滤器的 Order
设置任何值):
- The Controller OnActionExecuting
- The Global filter OnActionExecuting
- The Class filter OnActionExecuting
- The Method filter OnActionExecuting
- The Method filter OnActionExecuted
- The Class filter OnActionExecuted
- The Global filter OnActionExecuted
- The Controller OnActionExecuted
注解
当过滤器将启动运行、需要决定过滤器执行顺序时,IOrderedFilter
会向外宣布自己的作用范围。过滤器首先通过 order 来排序,然后通过作用范围来决定。如果不设置,则 Order 默认为 0。
为在基于作用范围的排序中修改默认值,你须在类一级(class-level)或方法一级(method-level)的过滤器上显式设置 Order
属性。比如为方法一级的特性增加 Order=-1
:
[MyFilter(Name = "Method Level Attribute", Order=-1)]
这种情况下,小于 0 的值将确保该过滤器在全局过滤器和类一级过滤器之前运行(假设它们的 Order
属性均未设置)。
新的排序可能是这样的:
- The Controller OnActionExecuting
- The Method filter OnActionExecuting
- The Global filter OnActionExecuting
- The Class filter OnActionExecuting
- The Class filter OnActionExecuted
- The Global filter OnActionExecuted
- The Method filter OnActionExecuted
- The Controller OnActionExecuted
注解
Controller
类的方法总是在所有过滤器之前和之后运行。这些方法并未实现为IFilter
实现,同时它们不参与IFilter
的排序算法。
授权过滤器
授权过滤器 控制对 action 方法的访问,也是过滤器管道中第一个被执行的过滤器。它们只有一个前置阶段,不像其它大多数过滤器支持前置阶段方法和后置阶段方法。只有当你使用自己的授权框架时才需要定制授权过滤器。谨记勿在授权过滤器内抛出异常,这是因为所抛出的异常不会被处理(异常过滤器也不会处理它们)。此时记录该问题或寻求其它办法。
更多请访问 Authorization
资源过滤器
资源过滤器 要么实现 IResourceFilter
接口,要么实现 IAsyncResourceFilter
接口,它们执行于大多数过滤器管道(只有 授权过滤器 在其之前运行,其余所有过滤器以及 Action 处理均出现在其 OnResourceExecuting
和 OnResourceExecuted
方法之间)。当你需要短路绝大多数正在进行的请求时,资源过滤器特别有用。资源过滤器的一个典型例子是缓存,如果响应已经被缓存,过滤器会立即将之置为结果以避免后续 Action 的多余操作过程。
上面所说的是一个 短路资源过滤器 的例子。下例是一个非常简单的缓存实现(请勿将之用于生产环境),只能与 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);
}
}
}
}
在 OnResourceExecuting
中,如果结果已经在静态字段缓存中,Result
属性将被设置到 context
上,同时 Action 被短路并返回缓存的结果。在 OnResourceExecuted
方法中,如果当前其请求的键未被使用过,那么 Result
就会被保存到缓存中,用于之后的请求。
如下所示,把这个过滤器用于类或方法之上:
[TypeFilter(typeof(NaiveCacheResourceFilterAttribute))]
public class CachedController : Controller
{
public IActionResult Index()
{
return Content("This content was generated at " + DateTime.Now);
}
}
Action 过滤器
Action 过滤器 要么实现 IActionFilter
接口,要么实现 IAsyncActionFilter
接口,它们可以在 action 方法执行的前后被执行。Action 过滤器非常适合放置诸如查看模型绑定结果、或是修改控制器或输入到 action 方法的逻辑。另外,action 过滤器可以查看并直接修改 action 方法的结果。
OnActionExecuting
方法在 action 方法执行之前运行,因此它可以通过改变 ActionExecutingContext.ActionArguments
来控制 action 的输入,或是通过 ActionExecutingContext.Controller
控制控制器(Controller)。OnActionExecuting
方法可以通过设置 ActionExecutingContext.Result
来短路 action 方法的操作及其后续的过滤器。OnActionExecuting
方法通过抛出异常也可以阻止 action 方法和后续过滤器的处理,但会当做失败(而不是成功)的结果来处理.
OnActionExecuted
方法在 action 方法执行之后才执行,并且可以通过 ActionExecutedContext.Result
属性查看或控制 action 的结果。如果 action 在执行时被其它过滤器短路,则 ActionExecutedContext.Canceled
将会被置为 true。如果 action 或后续的 action 过滤器抛出异常,则 ActionExecutedContext.Exception
会被设置为一个非空值。有效「处理」完异常后把 ActionExecutedContext.Exception
设置为 null,那么 ActionExectedContext.Result
会像从 action 方法正常返回值那样被处理。
对于 IAsyncActionFilter
接口来说,它的 OnActionExecutionAsync
方法结合了 OnActionExecuting
和 OnActionExecuted
的所有能力。调用 await next()
后,ActionExecutionDelegate
将会执行所有的后续 action 过滤器以及 action 方法,并返回 ActionExecutedContext
。
如果想要在 OnActionExecutionAsync
内部短路,那么就为 ActionExecutingContext.Result
分配一个结果实例,并且不要调用 ActionExecutionDelegate
即可。
异常过滤器
异常过滤器 实现了 IExceptionFilter
接口或 IAsyncExceptionFilter
接口。
异常过滤器用于处理「未处理异常」,包括发生在 Controller 创建及 模型绑定 期间出现的异常。它们只在管道内发生异常时才会被调用。它们提供了一个单一的位置实现应用程序内的公共异常处理策略。框架提供了抽象的 ExceptionFilterAttribute ,你根据自己的需要继承这个类。异常过滤器适用于捕获 MVC Action 内出现的异常,但它们不及错误处理中间件(error handling middleware)灵活。一般来讲优先使用中间件,只有在需要做一些基于所选 MVC Action 的、有别于错误处理的工作时才选择使用过滤器。
提示
对于应用程序中不同 action 需要使用不同的错误处理方式,并向 Views/HTML 暴露 API 端点或 action 的错误处理结果。API 端点用 JSON 返回错误信息,而基于视图的 action 则返回错误页面(HTML 页面)。
异常过滤器不应有两个事件(对于前置或后置而言),它们只实现 OnException
(或 OnExceptionAsync
)。以参数形式传入 OnException
的 ExceptionContext
包含了所发生的 Exception
。如果把 context.Exception
设置为 null,其效果相当于你已处理该异常,所以该次请求会像没发生过异常那样继续处理(一般会返回 HTTP 200 OK 状态)。下例过滤器中使用定制的开发者错误视图来显示开发环境中应用程序所出现异常的详细信息:
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;
}
}
}
结果过滤器
实现了 IResultFilter
或 IAsyncResultFilter
接口的 结果过滤器 在 Action Result 执行体的周围执行。当 Action 或 Action 过滤器产生 Action 结果时,只有成功运行的才会执行结果过滤器。如果异常过滤器处理了异常,那么结果过滤器就不会运行——除非异常过滤器将异常设置为null(Exception = null
)。
注解
正在执行的结果种类取决于相关 Action。MVC Action 所返回的 View 将包含 Razor(将其作为正在处理的ViewResult
的一部分)。API 方法则将执行一些序列化工作作为其执行结果的一部分。了解更多请移步 action 结果。
结果过滤器适用于任何需要直接环绕 View 或格式化处理的逻辑。结果过滤器可以替换或更改 Action 结果(而后者负责产生响应)。
OnResultExecuting
方法运行于 Action 结果执行之前,故其可通过 ResultExecutingContext.Result
操作 Action 结果。如果将 ResultExecutingContext.Cancel
设置为 true,则 OnResultExecuting
方法可短路 Action 结果以及后续结果过滤器的执行。如果发生了短路,MVC 将不会修改响应,所以当发生短路时,为避免生成空响应,你一般应该直接去修改响应对象。如果在 OnResultExecuting
方法内抛出异常,那么也将阻止 Action 结果以及后续过滤器的执行,但会被当做失败结果(而非成功结果)。
OnResultExecuted
方法运行于 Action 结果执行之后。也就是说,如果没有抛出异常,响应可能就会被发送到客户端且不可再修改。如果 Action 结果在执行中被其它过滤器短路,则 ResultExecutedContext.Canceled
将被置为 true。如果 Action 结果或后续结果过滤器抛出异常,则 ResultExecutedContext.Exception
将被置为非空值(non-null value)。把 ResultExecutedContext.Exception
设置为 null 后会影响到异常的“处理”,这将阻止异常在之后的管道内被 MVC 重新抛出。如果在结果过滤器内处理异常,需要确定此处是否适合将某些数据写入响应中。如果 Action 结果在执行中途抛出异常,而 header 也已被更新到客户端,那么将没有任何可靠的机制来发送失败代码。
对于 IAsyncResultFilter
的 OnResultExecutionAsync
方法来讲,它具有 OnResultExecuting
和 OnResultExecuted
的功能。在 ResultExecutionDelegate
上调用 await next()
将执行后续的结果过滤器和 Action 结果,并返回 ResultExecutedContext
。如果将 ResultExecutingContext.Cancel
值为 true 并不调用 ResultExectionDelegate
,则将在内部短路 OnResultExecutionAsync
。
你可以覆盖内建的 ResultFilterAttribute
特性,创建定制的结果过滤器, AddHeaderAttribute
类便是一例结果过滤器。
提示
若你需要为响应增加 header,在 Action 结果执行前如是做。否则响应就会被发送到客户端,届时改之晚矣。故对于结果过滤器而言,为响应增加 header 需要在OnResultExecuting
中处理(而不是在OnResultExecuted
中)。
过滤器对比中间件
一般情况下,过滤器用于处理业务与应用程序的横切关注点。它的用法很像 中间件 。从能力上来讲过滤器酷似中间件,但过滤器的作用范围很大,因此允许你将它插入到应用程序中需要使用到它的场合中,比如在视图之前或在模型绑定之后。过滤器是 MVC 的一部分,可以访问 MVC 的上下文以及构造函数。比方说,中间件不能简单地直接察觉请求中模型验证是否生成了错误并对此作出响应,而过滤器却能做到。
如果想要尝试一下过滤器,可以下载、测试并修改样例 。