ASP.NET Core in Action 21 The MVC and Razor Pages filter pipeline

21 The MVC and Razor Pages filter pipeline
21 MVC 和 Razor Pages 筛选器管道

This chapter covers
本章涵盖
• The filter pipeline and how it differs from middleware
过滤器管道及其与中间件的区别
• The different types of filters
过滤器的不同类型的
• Filter ordering
过滤器排序

Part 3 of this book has covered the Model-View-Controller (MVC) and Razor Pages frameworks of ASP.NET Core in some detail. You learned how routing is used to select a Razor Page or action to execute. You also saw model binding, validation, and how to generate a response by returning an IActionResult from your actions and page handlers. In this chapter I’m going to head deeper into the MVC/Razor Pages frameworks and look at the filter pipeline, sometimes called the action invocation pipeline, which is analogous to the minimal API endpoint filter pipeline you learned about in chapter 5.
本书的第 3 部分详细介绍了 ASP.NET Core 的模型-视图-控制器 (MVC) 和 Razor Pages 框架。你了解了如何使用路由来选择要执行的 Razor 页面或作。您还了解了模型绑定、验证以及如何通过从作和页面处理程序返回 IActionResult 来生成响应。在本章中,我将更深入地研究 MVC/Razor Pages 框架,并查看筛选器管道,有时称为作调用管道,它类似于您在第 5 章中了解的最小 API 端点筛选器管道。

MVC and Razor Pages use several built-in filters to handle cross-cutting concerns, such as authorization (controlling which users can access which action methods and pages in your application). Any application that has the concept of users will use authorization filters as a minimum, but filters are much more powerful than this single use case. In sections 21.1 and 21.2 you’ll learn about all the different types of filters and how they combine to create the MVC filter pipeline for a request that reaches the MVC or Razor Pages framework.
MVC 和 Razor Pages 使用多个内置筛选器来处理横切关注点,例如授权(控制哪些用户可以访问应用程序中的哪些作方法和页面)。任何具有用户概念的应用程序都将至少使用授权过滤器,但过滤器比这个单一用例强大得多。在第 21.1 节和第 21.2 节中,您将了解所有不同类型的筛选器,以及它们如何组合起来为到达 MVC 或 Razor Pages 框架的请求创建 MVC 筛选器管道。

Think of the MVC filter pipeline as a mini middleware pipeline running inside the MVC and Razor Pages frameworks, like the minimal API endpoint filter pipeline. Like the middleware pipeline in ASP.NET Core, the MVC filter pipeline consists of a series of components connected as a pipe, so the output of one filter feeds into the input of the next. In section 21.3 we’ll look at the similarities and differences between these two pipelines, and when you should choose one over the other.
将 MVC 筛选器管道视为在 MVC 和 Razor Pages 框架内运行的微型中间件管道,就像最小 API 终结点筛选器管道一样。与 ASP.NET Core 中的中间件管道一样,MVC 筛选器管道由一系列作为管道连接的组件组成,因此一个筛选器的输出会馈送到下一个筛选器的输入中。在 Section 21.3 中,我们将了解这两个 pipelines 之间的相似之处和不同之处,以及何时应该选择一个而不是另一个。

In section 21.4 you’ll see how to create a simple custom filter. Rather than focus on the functionality of the filter itself, you’ll learn how to apply it to multiple endpoints in section 21.5. In section 21.6 you’ll see how the choice of where you apply your attributes affects the order in which your filters execute.
在 Section 21.4 中,您将看到如何创建简单的自定义过滤器。您将学习 21.5 节中的如何将它应用于多个端点,而不是关注过滤器本身的功能。在 Section 21.6 中,您将看到选择应用属性的位置如何影响过滤器的执行顺序。

The filter pipeline is a complex topic, but it can enable some advanced behaviors in your app and potentially reduce overall complexity. In this chapter you’ll learn the basics of the pipeline and how it works. In chapter 22 we dig into practical examples of filters, looking at the filters that come out of the box in ASP.NET Core, as well as building custom filters to extract common code from your controllers and Razor Pages.
筛选管道是一个复杂的主题,但它可以在您的应用中启用一些高级行为,并可能降低整体复杂性。在本章中,您将学习管道的基础知识及其工作原理。在第 22 章中,我们深入探讨了筛选器的实际示例,查看了 ASP.NET Core 中开箱即用的筛选器,并构建了自定义筛选器以从控制器和 Razor 页面中提取常见代码。

Before we can start writing code, we should get to grips with the basics of the filter pipeline. The first section of this chapter explains what the pipeline is, why you might want to use it, and how it differs from the middleware pipeline.
在开始编写代码之前,我们应该先了解过滤器管道的基础知识。本章的第一部分介绍了管道是什么,为什么您可能希望使用它,以及它与中间件管道有何不同。

21.1 Understanding the MVC filter pipeline

21.1 了解 MVC 过滤器管道

In this section you’ll learn all about the MVC filter pipeline. You’ll see where it fits in the life cycle of a typical request and the roles of the six types of filters available.
在本节中,您将了解有关 MVC 筛选器管道的所有信息。您将看到它在典型请求的生命周期中的位置,以及可用的六种筛选器的角色。

The filter pipeline is a relatively simple concept in that it provides hooks into the normal MVC request, as shown in figure 21.1. For example, say you wanted to ensure that users can create or edit products on an e-commerce app only if they’re logged in. The app would redirect anonymous users to a login page instead of executing the action.
过滤器管道是一个相对简单的概念,因为它为普通的 MVC 请求提供了钩子,如图 21.1 所示。例如,假设您希望确保用户只有在登录后才能在电子商务应用程序上创建或编辑产品。该应用程序会将匿名用户重定向到登录页面,而不是执行作。

alt text

Figure 21.1 Filters run at multiple points in the EndpointMiddleware as part of the normal handling of an MVC request. A similar pipeline exists for Razor Page requests.
图 21.1 过滤器在 EndpointMiddleware 中的多个点运行,作为 MVC 请求的正常处理的一部分。Razor Page 请求存在类似的管道。

Without filters, you’d need to include the same code to check for a logged-in user at the start of each specific action method. With this approach, the MVC framework would still execute the model binding and validation, even if the user were not logged in.
如果没有筛选器,则需要在每个特定作方法的开头包含相同的代码来检查已登录的用户。使用这种方法,MVC 框架仍将执行模型绑定和验证,即使用户未登录。

With filters, you can use the hooks in the MVC request to run common code across all requests or a subset of requests. This way you can do a wide range of things, such as
借助筛选器,您可以使用 MVC 请求中的挂钩在所有请求或请求子集中运行通用代码。通过这种方式,您可以执行各种作,例如

• Ensure that a user is logged in before an action method, model binding, or validation runs.
确保在作方法、模型绑定或验证运行之前登录用户。
• Customize the output format of particular action methods.
自定义特定作方法的输出格式。
• Handle model validation failures before an action method is invoked.
在调用作方法之前处理模型验证失败。
• Catch exceptions from an action method and handle them in a special way.
从作方法捕获异常,并以特殊方式处理它们。

In many ways, the MVC filter pipeline is like an extra middleware pipeline, restricted to MVC and Razor Pages requests only. Like middleware, filters are good for handling cross-cutting concerns for your application and are useful tools for reducing code duplication in many cases.
在许多方面,MVC 筛选器管道就像一个额外的中间件管道,仅限于 MVC 和 Razor Pages 请求。与 middleware 一样,过滤器非常适合处理应用程序的横切关注点,并且在许多情况下是减少代码重复的有用工具。

The linear1 view of an MVC request and the filter pipeline that I’ve used so far doesn’t quite match up with how these filters execute. There are five types of filters that apply to MVC requests, each of which runs at a different stage in the MVC framework, as shown in figure 21.2.
到目前为止,我使用的 MVC 请求的 linear1 视图和筛选器管道与这些筛选器的执行方式并不完全匹配。有五种类型的过滤器适用于 MVC 请求,每一种都在 MVC 框架的不同阶段运行,如图 21.2 所示。

alt text

Figure 21.2 The MVC filter pipeline, including the five filter stages. Some filter stages (resource, action, and result) run twice, before and after the remainder of the pipeline.
图 21.2 MVC 过滤器管道,包括 5 个过滤器阶段。某些筛选阶段(resource、action 和 result)在管道的其余部分之前和之后运行两次。

Each filter stage lends itself to a particular use case, thanks to its specific location in the pipeline, with respect to model binding, action execution, and result execution:
每个过滤器阶段都适用于特定的用例,这要归功于它在管道中的特定位置,包括模型绑定、作执行和结果执行:

• Authorization filters—These run first in the pipeline, so they’re useful for protecting your APIs and action methods. If an authorization filter deems the request unauthorized, it short-circuits the request, preventing the rest of the filter pipeline (or action) from running.
授权过滤器 - 这些过滤器首先在管道中运行,因此它们可用于保护您的 API 和作方法。如果授权筛选条件认为请求未经授权,则会使请求短路,从而阻止筛选条件管道(或作)的其余部分运行。
• Resource filters—After authorization, resource filters are the next filters to run in the pipeline. They can also execute at the end of the pipeline, in much the same way that middleware components can handle both the incoming request and the outgoing response. Alternatively, resource filters can completely short-circuit the request pipeline and return a response directly.
资源过滤器 - 授权后,资源过滤器是管道中运行的下一个过滤器。它们还可以在管道的末尾执行,就像中间件组件可以同时处理传入请求和传出响应一样。或者,资源筛选条件可以完全短路请求管道并直接返回响应。

Thanks to their early position in the pipeline, resource filters can have a variety of uses. You could add metrics to an action method; prevent an action method from executing if an unsupported content type is requested; or, as they run before model binding, control the way model binding works for that request.
由于它们在管道中的早期位置,资源过滤器可以有多种用途。您可以向作方法添加度量;在请求不受支持的内容类型时阻止执行作方法;或者,当它们在模型绑定之前运行时,控制模型绑定对该请求的工作方式。

• Action filters—Action filters run immediately before and after an action method is executed. As model binding has already happened, action filters let you manipulate the arguments to the method—before it executes—or they can short-circuit the action completely and return a different IActionResult. Because they also run after the action executes, they can optionally customize an IActionResult returned by the action before the action result is executed.
作筛选器 -作筛选器在执行作方法之前和之后立即运行。由于模型绑定已经发生,因此作筛选器允许您在方法执行之前作方法的参数,或者它们可以完全短路作并返回不同的 IActionResult。由于它们也在作执行后运行,因此可以选择在执行作结果之前自定义作返回的 IActionResult。
• Exception filters—Exception filters catch exceptions that occur in the filter pipeline and handle them appropriately. You can use exception filters to write custom, MVC-specific error-handling code, which can be useful in some situations. For example, you could catch exceptions in API actions and format them differently from exceptions in your Razor Pages.
异常过滤器 - 异常过滤器可捕获过滤器管道中发生的异常并对其进行适当处理。您可以使用异常筛选器编写特定于 MVC 的自定义错误处理代码,这在某些情况下可能很有用。例如,可以在 API作中捕获异常,并对其进行不同于 Razor Pages 中的异常格式设置。
• Result filters—Result filters run before and after an action method’s IActionResult is executed. You can use result filters to control the execution of the result or even to short-circuit the execution of the result.
结果筛选器 - 结果筛选器在执行作方法的 IActionResult 之前和之后运行。您可以使用结果筛选器来控制结果的执行,甚至可以缩短结果的执行。

Exactly which filter you pick to implement will depend on the functionality you’re trying to introduce. Want to short-circuit a request as early as possible? Resource filters are a good fit. Need access to the action method parameters? Use an action filter.
您选择实施哪个过滤器将取决于您尝试引入的功能。想要尽早使请求短路?资源筛选器非常适合。需要访问作方法参数?使用作筛选器。

Think of the filter pipeline as a small middleware pipeline that lives by itself in the MVC framework. Alternatively, you could think of filters as hooks into the MVC action invocation process that let you run code at a particular point in a request’s life cycle.
将过滤器管道视为一个小型中间件管道,它独立存在于 MVC 框架中。或者,您可以将筛选器视为 MVC作调用过程的挂钩,允许您在请求生命周期的特定点运行代码。

NOTE The design of the MVC filter pipeline is quite different from the minimal API endpoint filter pipeline you saw in chapter 5. The endpoint filter pipeline is linear and doesn’t have multiple types of filters.
注意:MVC 过滤器管道的设计与您在第 5 章中看到的最小 API 端点过滤器管道完全不同。终端节点筛选条件管道是线性的,没有多种类型的筛选条件。

This section described how the filter pipeline works for MVC and Web API controllers; Razor Pages use an almost-identical filter pipeline.
本部分介绍了筛选器管道如何用于 MVC 和 Web API 控制器;Razor Pages 使用几乎相同的筛选管道。

21.2 The Razor Pages filter pipeline

21.2 Razor Pages 筛选器管道

The Razor Pages framework uses the same underlying architecture as MVC and Web API controllers, so it’s perhaps not surprising that the filter pipeline is virtually identical. The only difference between the pipelines is that Razor Pages do not use action filters. Instead, they use page filters, as shown in figure 21.3.
Razor Pages 框架使用与 MVC 和 Web API 控制器相同的底层体系结构,因此筛选器管道几乎相同可能不足为奇。管道之间的唯一区别是 Razor Pages 不使用作筛选器。相反,它们使用页面过滤器,如图 21.3 所示。

alt text

Figure 21.3 The Razor Pages filter pipeline, including the five filter stages. Authorization, resource, exception, and result filters execute in exactly the same way as for the MVC pipeline. Page filters are specific to Razor Pages and execute in three places: after page hander selection, after model binding and validation, and after page handler execution.
图 21.3 Razor Pages 筛选器管道,包括 5 个筛选器阶段。授权、资源、异常和结果筛选器的执行方式与 MVC 管道的执行方式完全相同。页面筛选器特定于 Razor 页面,并在三个位置执行:选择页面处理程序后、模型绑定和验证后以及页面处理程序执行后。

The authorization, resource, exception, and result filters are exactly the same filters you saw for the MVC pipeline. They execute in the same way, serve the same purposes, and can be short-circuited in the same way.
authorization、resource、exception 和 result 过滤器与您在 MVC 管道中看到的过滤器完全相同。它们以相同的方式执行,服务于相同的目的,并且可以以相同的方式短路。

NOTE These filters are literally the same classes shared between the Razor Pages and MVC frameworks.
注意:这些筛选器实际上是 Razor Pages 和 MVC 框架之间共享的相同类。

The difference with the Razor Pages filter pipeline is that it uses page filters instead of action filters. By contrast with other filter types, page filters run three times in the filter pipeline:
与 Razor Pages 筛选器管道的不同之处在于,它使用页面筛选器而不是作筛选器。与其他筛选器类型相比,页面筛选器在筛选器管道中运行三次:

• After page handler selection—After the resource filters have executed, a page handler is selected, based on the request’s HTTP verb and the {handler} route value, as you learned in chapter 15. After page handler selection, a page filter method executes for the first time. You can’t short-circuit the pipeline at this stage, and model binding and validation have not yet executed.
选择页面处理程序后 - 执行资源过滤器后,将根据请求的 HTTP 动词和 {handler} 路由值选择页面处理程序,如第 15 章所述。选择页面处理程序后,将首次执行页面筛选方法。在此阶段,您不能使管道短路,并且模型绑定和验证尚未执行。

• After model binding—After the first page filter execution, the request is model-bound to the Razor Page’s binding models and is validated. This execution is highly analogous to the action filter execution for API controllers. At this point you could manipulate the model-bound data or short-circuit the page handler execution completely by returning a different IActionResult.
模型绑定后 - 在执行第一个页面筛选器后,请求将模型绑定到 Razor 页面的绑定模型并进行验证。此执行与 API 控制器的 action filter 执行高度相似。此时,您可以通过返回不同的 IActionResult 来作模型绑定数据或完全短路页面处理程序执行。

• After page handler execution—If you don’t short-circuit the page handler execution, the page filter runs a third and final time after the page handler has executed. At this point you could customize the IActionResult returned by the page handler before the result is executed.
页面处理程序执行后 - 如果不使页面处理程序执行短路,则页面过滤器将在页面处理程序执行后第三次也是最后一次运行。此时,您可以在执行结果之前自定义页面处理程序返回的 IActionResult。

The triple execution of page filters makes it a bit harder to visualize the pipeline, but you can generally think of them as beefed-up action filters. Everything you can do with an action filter, you can do with a page filter, and you can hook in after page handler selection if necessary.
页面过滤器的三重执行使得可视化管道有点困难,但您通常可以将它们视为增强的作过滤器。你可以用 action filter 做的所有事情,都可以用 page filter 做,如果需要,你可以在 Page handler 选择后挂接。

Tip Each execution of a filter executes a different method of the appropriate interface, so it’s easy to know where you are in the pipeline and to execute a filter in only one of its possible locations if you wish.
提示:每次执行筛选条件都会执行相应接口的不同方法,因此很容易知道您在管道中的位置,并且如果您愿意,只需在其一个可能的位置执行筛选条件。

One of the main questions I hear when people learn about filters in ASP.NET Core is “Why do we need them?” If the filter pipeline is like a mini middleware pipeline, why not use a middleware component directly, instead of introducing the filter concept? That’s an excellent point, which I’ll tackle in the next section.
当人们了解 ASP.NET Core 中的过滤器时,我听到的主要问题之一是“我们为什么需要它们?如果 filter pipeline 就像一个迷你的 middleware pipeline ,为什么不直接使用一个 middleware 组件,而不是引入 filter 概念呢?这是一个很好的观点,我将在下一节中讨论。

21.3 Filters or middleware: Which should you choose?

21.3 过滤器或中间件:您应该选择哪个?

The filter pipeline is similar to the middleware pipeline in many ways, but there are several subtle differences that you should consider when deciding which approach to use. The considerations are essentially the same as those for the minimal API endpoint filter I discussed in chapter 5. MVC filters and middleware are similar in three ways:
filter 管道在许多方面与中间件管道相似,但在决定使用哪种方法时,应考虑几个细微的差异。这些注意事项与我在第 5 章中讨论的最小 API 端点过滤器的注意事项基本相同。MVC 筛选器和中间件在三个方面相似:

• Requests pass through a middleware component on the way “in,” and responses pass through again on the way “out.” Resource, action, and result filters are also two-way, though authorization and exception filters run only once for a request, and page filters run three times.
请求在“in”途中通过中间件组件,响应在“out”途中再次传递。资源、作和结果筛选器也是双向的,但授权和异常筛选器只为请求运行一次,而页面筛选器运行三次。
• Middleware can short-circuit a request by returning a response instead of passing it on to later middleware. MVC and page filters can also short-circuit the filter pipeline by returning a response.
中间件可以通过返回响应而不是将其传递给后续中间件来使请求短路。MVC 和页面筛选器还可以通过返回响应来使筛选器管道短路。
• Middleware is often used for cross-cutting application concerns, such as logging, performance profiling, and exception handling. Filters also lend themselves to cross-cutting concerns.
中间件通常用于横切应用程序问题,例如日志记录、性能分析和异常处理。过滤器还适用于横切关注点。

Filters and middleware also differ primarily in three ways:
筛选器和中间件也主要在三个方面有所不同:

• Middleware can run for all requests; filters run only for requests that reach the EndpointMiddleware and execute a controller action or Razor Page handler.
中间件可以针对所有请求运行筛选器仅针对到达 EndpointMiddleware 并执行控制器作或 Razor Page 处理程序的请求运行。
• Filters have access to MVC constructs such as ModelState and IActionResults. Middleware in general is independent from MVC and Razor Pages and works at a lower level, so it can’t use these concepts.
筛选器可以访问 MVC 构造,例如 ModelState 和 IActionResults。中间件通常独立于 MVC 和 Razor Pages,并且在较低级别工作,因此它不能使用这些概念。
• Filters can be easily applied to a subset of requests, such as all actions on a single controller or a single Razor Page. Middleware generally applies to all requests that reach a given point in the middleware pipeline.
筛选器可以轻松应用于请求的子集,例如单个控制器或单个 Razor 页面上的所有作。中间件通常适用于到达中间件管道中给定点的所有请求。

As for the endpoint filter pipeline, I like to think of middleware versus MVC filters as a question of specificity. Middleware is the more general concept, so it has the wider reach. But if you need to access to MVC constructs or want to behave differently for some MVC actions or Razor Pages, you should consider using a filter.
至于端点过滤器管道,我喜欢将中间件与 MVC 过滤器视为一个特异性问题。中间件是更通用的概念,因此它的范围更广。但是,如果需要访问 MVC 构造或希望对某些 MVC作或 Razor Pages 采取不同的行为,则应考虑使用筛选器。

The middleware-versus-filters argument is a subtle one, and it doesn’t matter which you choose as long as it works for you. You can even use middleware components inside the MVC filter pipeline, effectively turning a middleware component into a filter!
middleware-versus-filters 的参数是一个微妙的参数,只要它适合你,你选择哪一个并不重要。您甚至可以在 MVC 过滤器管道中使用中间件组件,从而有效地将中间件组件转换为过滤器!

Tip The middleware-as-filters feature was introduced in ASP.NET Core 1.1 and is also available in later versions. The canonical use case is for localizing requests to multiple languages. I have a blog series on how to use the feature here: http://mng.bz/RXa0.
提示middleware-as-filters 功能是在 ASP.NET Core 1.1 中引入的,在以后的版本中也可用。规范用例是将请求本地化为多种语言。我有一个关于如何使用该功能的博客系列:http://mng.bz/RXa0

Filters can be a little abstract in isolation, so in the next section we’ll look at some code and learn how to write a custom MVC filter in ASP.NET Core.
筛选器可以单独使用一些抽象,因此在下一节中,我们将查看一些代码并学习如何在 ASP.NET Core 中编写自定义 MVC 筛选器。

21.4 Creating a simple filter

21.4 创建简单过滤器

In this section, I show you how to create your first filters; in section 21.5 you’ll see how to apply them to MVC controllers and actions. We’ll start small, creating filters that only write to the console, but in chapter 22 we look at some more practical examples and discuss some of their nuances.
在本节中,我将向您展示如何创建您的第一个过滤器;在 Section 21.5 中,您将看到如何将它们应用于 MVC 控制器和作。我们将从小处着手,创建仅写入控制台的过滤器,但在第 22 章中,我们将查看一些更实际的示例并讨论它们的一些细微差别。

You implement a filter for a given stage by implementing one of a pair of interfaces, one synchronous (sync) and one asynchronous (async):
您可以通过实现一对接口之一(一个同步 (sync)和一个异步 (async))来为给定阶段实现筛选器:

• Authorization filters—IAuthorizationFilter or IAsyncAuthorizationFilter
授权筛选器 - IAuthorizationFilter 或 IAsyncAuthorizationFilter
• Resource filters—IResourceFilter or IAsyncResourceFilter
资源筛选器 - IResourceFilter 或 IAsyncResourceFilter
• Action filters—IActionFilter or IAsyncActionFilter
动作筛选器 - IActionFilter 或 IAsyncActionFilter
• Page filters—IPageFilter or IAsyncPageFilter
页面筛选器 - IPageFilter 或 IAsyncPageFilter
• Exception filters—IExceptionFilter or IAsyncExceptionFilter
异常筛选器 - IExceptionFilter 或 IAsyncExceptionFilter
• Result filters—IResultFilter or IAsyncResultFilter
结果筛选器 - IResultFilter 或 IAsyncResultFilter

You can use any plain old CLR object (POCO) class to implement a filter, but you’ll typically implement them as C# attributes, which you can use to decorate your controllers, actions, and Razor Pages, as you’ll see in section 21.5. You can achieve the same results with either the sync or async interface, so which you choose should depend on whether any services you call in the filter require async support.
您可以使用任何普通的旧 CLR 对象 (POCO) 类来实现过滤器,但您通常会将它们实现为 C# 属性,您可以使用这些属性来装饰您的控制器、作和 Razor 页面,如第 21.5 节所示。您可以使用 sync 或 async 接口实现相同的结果,因此您选择哪个接口取决于您在过滤器中调用的任何服务是否需要异步支持。

NOTE You should implement either the sync interface or the async interface, not both. If you implement both, only the async interface will be used.
注意:您应该实现 sync 接口或 async 接口,而不是两者兼而有之。如果同时实现这两个接口,则仅使用异步接口。

Listing 21.1 shows a resource filter that implements IResourceFilter and writes to the console when it executes. The OnResourceExecuting method is called when a request first reaches the resource filter stage of the filter pipeline. By contrast, the OnResourceExecuted method is called after the rest of the pipeline has executed: after model binding, action execution, result execution, and all intermediate filters have run.
清单 21.1 显示了一个资源过滤器,它实现 IResourceFilter 并在执行时写入控制台。当请求首次到达筛选管道的资源筛选阶段时,将调用 OnResourceExecuting 方法。相比之下,OnResourceExecuted 方法是在管道的其余部分执行之后调用的:在模型绑定、作执行、结果执行和所有中间筛选器运行之后。

Listing 21.1 Example resource filter implementing IResourceFilter
清单 21.1 实现 IResourceFilter 的示例资源过滤器

public class LogResourceFilter : Attribute, IResourceFilter
{
    public void OnResourceExecuting(          #A
        ResourceExecutingContext context)    #B
    {
        Console.WriteLine("Executing!");
    }

    public void OnResourceExecuted(             #C
        ResourceExecutedContext context)    #D
    {
        Console.WriteLine("Executed");
    }
}

❶ Executed at the start of the pipeline, after authorization filters
在管道开始时执行,在授权过滤器之后
❷ The context contains the HttpContext, routing details, and information about the current action.
上下文包含 HttpContext、路由详细信息和有关当前作的信息。
❸ Executed after model binding, action execution, and result execution
在模型绑定、作执行和结果执行之后执行
❹ Contains additional context information, such as the IActionResult returned by the action
包含其他上下文信息,例如作返回的 IActionResult

The interface methods are simple and are similar for each stage in the filter pipeline, passing a context object as a method parameter. Each of the two-method sync filters has an Executing and an Executed method. The type of the argument is different for each filter, but it contains all the details for the filter pipeline.
接口方法很简单,并且对于筛选器管道中的每个阶段都类似,将上下文对象作为方法参数传递。两种方法的同步筛选器中的每一个都有一个 Executing 和一个 Executed 方法。每个筛选条件的参数类型都不同,但它包含筛选条件管道的所有详细信息。

For example, the ResourceExecutingContext passed to the resource filter contains the HttpContext object itself, details about the route that selected this action, details about the action itself, and so on. Contexts for later filters contain additional details, such as the action method arguments for an action filter and the ModelState.
例如,传递给资源筛选器的 ResourceExecutingContext 包含 HttpContext 对象本身、有关选择此作的路由的详细信息、有关作本身的详细信息等。更高筛选器的上下文包含其他详细信息,例如作筛选器的作方法参数和 ModelState。

The context object for the ResourceExecutedContext method is similar, but it also contains details about how the rest of the pipeline executed. You can check whether an unhandled exception occurred, you can see if another filter from the same stage short-circuited the pipeline, or you can see the IActionResult used to generate the response.
ResourceExecutedContext 方法的上下文对象类似,但它还包含有关管道其余部分如何执行的详细信息。您可以检查是否发生了未经处理的异常,可以查看同一阶段中的另一个筛选器是否使管道短路,或者您可以查看用于生成响应的 IActionResult。

These context objects are powerful and are the key to advanced filter behaviors like short-circuiting the pipeline and handling exceptions. We’ll make use of them in chapter 22 when we create more complex filter examples.
这些上下文对象功能强大,是高级筛选行为(如短路管道和处理异常)的关键。我们将在第 22 章创建更复杂的过滤器示例时使用它们。

The async version of the resource filter requires implementing a single method, as shown in listing 21.2. As for the sync version, you’re passed a ResourceExecutingContext object as an argument, and you’re passed a delegate representing the remainder of the filter pipeline. You must call this delegate (asynchronously) to execute the remainder of the pipeline, which returns an instance of ResourceExecutedContext.
资源过滤器的异步版本需要实现一个方法,如清单 21.2 所示。对于同步版本,将向您传递一个 ResourceExecutingContext 对象作为参数,并传递一个表示筛选管道其余部分的委托。您必须(异步)调用此委托来执行管道的其余部分,该管道将返回 ResourceExecutedContext 的实例。

Listing 21.2 Example resource filter implementing IAsyncResourceFilter
列表 21.2 实现 IAsyncResourceFilter 的示例资源过滤器

public class LogAsyncResourceFilter : Attribute, IAsyncResourceFilter
{
public async Task OnResourceExecutionAsync( ❶
ResourceExecutingContext context,
ResourceExecutionDelegate next) ❷
{
Console.WriteLine("Executing async!"); ❸
ResourceExecutedContext executedContext = await next(); ❹
Console.WriteLine("Executed async!"); ❺
}
}

❶ Executed at the start of the pipeline, after authorization filters
在管道开始时执行,授权过滤器之后
❷ You’re provided a delegate, which encapsulates the remainder of the filter pipeline.
为您提供一个委托,它封装了过滤器管道的其余部分。
❸ Called before the rest of the pipeline executes
在管道的其余部分执行之前调用
❹ Executes the rest of the pipeline and obtains a ResourceExecutedContext instance
执行管道的其余部分并获取 ResourceExecutedContext 实例
❺ Called after the rest of the pipeline executes
在管道的其余部分执行之后调用

The sync and async filter implementations have subtle differences, but for most purposes they’re identical. I recommend implementing the sync version for simplicity, falling back to the async version only if you need to.
sync 和 async filter 实现有细微的差异,但在大多数情况下它们是相同的。为简单起见,我建议实现同步版本,仅在需要时回退到异步版本。

You’ve created a couple of filters now, so we should look at how to use them in the application. In the next section we’ll tackle two specific issues: how to control which requests execute your new filters and how to control the order in which they execute.
您现在已经创建了几个过滤器,因此我们应该看看如何在应用程序中使用它们。在下一节中,我们将解决两个具体问题:如何控制哪些请求执行您的新过滤器,以及如何控制它们的执行顺序。

21.5 Adding filters to your actions and Razor Pages

向动作和 Razor 页面添加过滤器

In section 21.3 I discussed the similarities and differences between middleware and filters. One of those differences is that filters can be scoped to specific actions or controllers so that they run only for certain requests. Alternatively, you can apply a filter globally so that it runs for every MVC action and Razor Page.
在 Section 21.3 中,我讨论了 middleware 和 filters 之间的异同。其中一个区别是,过滤器的范围可以限定为特定的作或控制器,以便它们仅针对特定请求运行。或者,您可以全局应用筛选器,以便它针对每个 MVC作和 Razor 页面运行。

By adding filters in different ways, you can achieve several different results. Imagine you have a filter that forces you to log in to execute an action. How you add the filter to your app will significantly change your app’s behavior:
通过以不同的方式添加过滤器,您可以获得多种不同的结果。假设您有一个过滤器,它强制您登录以执行作。向应用程序添加过滤器的方式将显著改变应用程序的行为:

• Apply the filter to a single action or Razor Page. Anonymous users could browse the app as normal, but if they tried to access the protected action or Razor Page, they would be forced to log in.
将筛选器应用于单个作或 Razor 页面。匿名用户可以正常浏览应用程序,但如果他们尝试访问受保护的作或 Razor 页面,他们将被迫登录。
• Apply the filter to a controller. Anonymous users could access actions from other controllers, but accessing any action on the protected controller would force them to log in.
将过滤器应用于控制器。匿名用户可以访问来自其他控制器的作,但访问受保护控制器上的任何作都会强制他们登录。
• Apply the filter globally. Users couldn’t use the app without logging in. Any attempt to access an action or Razor Page would redirect the user to the login page.
全局应用筛选器。用户如果不登录就无法使用该应用程序。任何访问作或 Razor 页面的尝试都会将用户重定向到登录页面。

NOTE ASP.NET Core comes with such a filter out of the box: AuthorizeFilter. I discuss this filter in chapter 22, and you’ll be seeing a lot more of it in chapter 24.
注意: ASP.NET Core 附带了这样一个开箱即用的筛选器:AuthorizeFilter。我在第 22 章中讨论了这个过滤器,您将在第 24 章中看到更多内容。

As I described in the previous section, you normally create filters as attributes, and for good reason: it makes it easy for you to apply them to MVC controllers, actions, and Razor Pages. In this section you’ll see how to apply LogResourceFilter from listing 21.1 to an action, a controller, a Razor Page, and globally. The level at which the filter applies is called its scope.
正如我在上一节中所描述的,您通常将筛选器创建为属性,这是有充分理由的:它使您可以轻松地将它们应用于 MVC 控制器、作和 Razor 页面。在本节中,您将了解如何将清单 21.1 中的 LogResourceFilter 应用于作、控制器、Razor Page 和全局应用。筛选器应用的级别称为其范围。

DEFINITION The scope of a filter refers to how many different actions it applies to. A filter can be scoped to the action method, to the controller, to a Razor Page, or globally.
定义:筛选器的范围是指它应用于多少个不同的作。筛选器的范围可以限定为作方法、控制器、Razor 页面或全局。

You’ll start at the most specific scope: applying filters to a single action. The following listing shows an example of an MVC controller that has two action methods, one with LogResourceFilter and one without.
您将从最具体的范围开始:将筛选条件应用于单个作。下面的清单显示了一个 MVC 控制器的示例,该控制器具有两个作方法,一个带有 LogResourceFilter,另一个没有。

Listing 21.3 Applying filters to an action method
清单 21.3 将过滤器应用于作方法

public class RecipeController : ControllerBase
{
    [LogResourceFilter]            #A
    public IActionResult Index()   #A
    {                              #A
        return Ok();               #A
    }                              #A
    public IActionResult View()   #B
    {                             #B
        return OK();              #B
    }                             #B
}

❶ LogResourceFilter runs as part of the pipeline when executing this action.
LogResourceFilter 在执行此作时作为管道的一部分运行。
❷ This action method has no filters at the action level.
此作方法在作级别没有筛选器。

Alternatively, if you want to apply the same filter to every action method, you could add the attribute at the controller scope, as in the next listing. Every action method in the controller uses LogResourceFilter without having to specifically decorate each method.
或者,如果要将相同的过滤器应用于每个作方法,则可以在控制器范围内添加该属性,如下一个清单所示。控制器中的每个 action method 都使用 LogResourceFilter,而不必专门修饰每个方法。

Listing 21.4 Applying filters to a controller
清单 21.4 将过滤器应用于控制器

[LogResourceFilter]                             #A
public class RecipeController : ControllerBase
{
    public IActionResult Index ()   #B
    {                               #B
        return Ok();                #B
    }                               #B
    public IActionResult View()     #B
    {                               #B
        return Ok();                #B
    }                               #B
}

❶ The LogResourceFilter is added to every action on the controller.
LogResourceFilter 被添加到控制器上的每个作中。
❷ Every action in the controller is decorated with the filter.
控制器中的每个作都用过滤器装饰。

For Razor Pages, you can apply attributes to your PageModel, as shown in the following listing. The filter applies to all page handlers in the Razor Page. It’s not possible to apply filters to a single page handler; you must apply them at the page level.
对于 Razor Pages,您可以将属性应用于 PageModel,如下面的清单所示。筛选器适用于 Razor 页面中的所有页面处理程序。无法将过滤器应用于单个页面处理程序;您必须在页面级别应用它们。

Listing 21.5 Applying filters to a Razor Page
清单 21.5 将过滤器应用于 Razor 页面

[LogResourceFilter]             #A
public class IndexModel : PageModel
{
    public void OnGet()    #B
    {                      #B
    }                      #B

    public void OnPost()   #B
    {                      #B
    }                      #B
}

❶ The LogResourceFilter is added to the Razor Page’s PageModel.
LogResourceFilter 已添加到 Razor 页面的 PageModel。
❷ The filter applies to every page handler in the page.
过滤器适用于页面中的每个页面处理程序。

Filters you apply as attributes to controllers, actions, and Razor Pages are automatically discovered by the framework when your application starts up. For common attributes, you can go one step further and apply filters globally without having to decorate individual classes.
当应用程序启动时,框架会自动发现作为属性应用于控制器、作和 Razor 页面的筛选器。对于通用属性,您可以更进一步,全局应用过滤器,而不必装饰单个类。

You add global filters in a different way from controller- or action-scoped filters—by adding a filter directly to the MVC services when configuring your controllers and Razor Pages. The next listing shows three equivalent ways to add a globally scoped filter.
添加全局筛选器的方式与控制器或作范围的筛选器不同,即在配置控制器和 Razor Pages 时直接向 MVC 服务添加筛选器。下一个清单显示了添加全局范围过滤器的三种等效方法。

Listing 21.6 Applying filters globally to an application
清单 21.6 将过滤器全局应用于应用程序

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>    #A
{
    options.Filters.Add(new LogResourceFilter());     #B
    options.Filters.Add(typeof(LogResourceFilter));   #C
    options.Filters.Add<LogResourceFilter>();    #D
});

❶ Adds filters using the MvcOptions object
使用 MvcOptions 对象添加过滤器
❷ You can pass an instance of the filter directly. . .
您可以直接传递过滤器的实例。 . .
❸ . . . or pass in the Type of the filter and let the framework create it.
. . . .或者传入过滤器的 Type 并让框架创建它。
❹ Alternatively, the framework can create a global filter using a generic type parameter.
或者,框架可以使用泛型类型参数创建全局过滤器。

You can configure the MvcOptions by using the AddControllers() overload. When you configure filters globally, they apply both to controllers and to any Razor Pages in your application. If you wish to configure a global filter for a Razor Pages application, there isn’t an overload for configuring the MvcOptions. Instead, you need to use the AddMvcOptions() extension method to configure the filters, as shown in the following listing.
您可以使用 AddControllers() 重载配置 MvcOptions。全局配置筛选器时,它们将同时应用于控制器和应用程序中的任何 Razor Pages。如果要为 Razor Pages 应用程序配置全局筛选器,则不会有用于配置 MvcOptions 的重载。相反,您需要使用 AddMvcOptions() 扩展方法来配置过滤器,如下面的清单所示。

Listing 21.7 Applying filters globally to a Razor Pages application
列表 21.7 将过滤器全局应用于 Razor Pages 应用程序

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.RazorPages()  #A
    .AddMvcOptions(options =>    #B
    {
        options.Filters.Add(new LogResourceFilter());     #C
        options.Filters.Add(typeof(LogResourceFilter));   #C
        options.Filters.Add<LogResourceFilter>();    #C
    });

❶ This method doesn’t let you pass a lambda to configure the MvcOptions.
此方法不允许您传递 lambda 来配置 MvcOptions。
❷ You must use an extension method to add the filters to the MvcOptions object.
必须使用扩展方法将筛选器添加到 MvcOptions 对象。
❸ You can configure the filters in any of the ways shown previously.
您可以按照前面显示的任何方式配置过滤器。

With potentially three different scopes in play, you’ll often find action methods that have multiple filters applied to them, some applied directly to the action method and others inherited from the controller or globally. Then the question becomes which filter runs first.
由于可能有三种不同的范围,您通常会发现应用了多个过滤器的作方法,其中一些直接应用于作方法,而另一些则从控制器或全局继承。然后问题就变成了哪个过滤器先运行。

21.6 Understanding the order of filter execution

21.6 了解过滤器的执行顺序

You’ve seen that the filter pipeline contains five stages, one for each type of filter. These stages always run in the fixed order I described in sections 21.1 and 21.2. But within each stage, you can also have multiple filters of the same type (for example, multiple resource filters) that are part of a single action method’s pipeline. These could all have multiple scopes, depending on how you added them, as you saw in the preceding section.
您已经看到 filter 管道包含 5 个阶段,每种类型的 filter 对应一个阶段。这些阶段始终按照我在第 21.1 节和第 21.2 节中描述的固定顺序运行。但在每个阶段中,您还可以拥有多个相同类型的筛选条件(例如,多个资源筛选条件),这些筛选条件是单个作方法的管道的一部分。这些范围可能都具有多个范围,具体取决于您添加它们的方式,如上一节所示。

In this section we’re thinking about the order of filters within a given stage and how scope affects this. We’ll start by looking at the default order and then move on to ways to customize the order to your own requirements.
在本节中,我们将考虑给定阶段中过滤器的顺序以及范围如何影响这一点。我们将首先查看默认顺序,然后继续讨论根据自己的要求自定义顺序的方法。

21.6.1 The default scope execution order

21.6.1 默认范围执行顺序

When thinking about filter ordering, it’s important to remember that resource, action, and result filters implement two methods: an Executing before method and an Executed after method. On top of that, page filters implement three methods! The order in which each method executes depends on the scope of the filter, as shown in figure 21.4 for the resource filter stage.
在考虑筛选器排序时,请务必记住,资源、作和结果筛选器实现两种方法:Executing before 方法和 Executed after 方法。最重要的是,页面过滤器实现了三种方法!每个方法的执行顺序取决于过滤器的范围,如图 21.4 所示,用于资源过滤器阶段。

alt text

Figure 21.4 The default filter ordering within a given stage, based on the scope of the filters. For the Executing method, globally scoped filters run first, followed by controller-scoped, and finally action-scoped filters. For the Executed method, the filters run in reverse order.
图 21.4 给定阶段中基于过滤器范围的默认过滤器排序。对于 Executing 方法,全局范围的筛选器首先运行,然后是控制器范围的筛选器,最后是作范围的筛选器。对于 Executed 方法,筛选器按相反的顺序运行。

By default, filters execute from the broadest scope (global) to the narrowest (action) when running the Executing method for each stage. The filters’ Executed methods run in reverse order, from the narrowest scope (action) to the broadest (global).
默认情况下,在为每个阶段运行 Executing 方法时,筛选器从最广泛的范围 (全局) 到最窄的 (作) 执行。筛选器的 Executed 方法按相反的顺序运行,从最窄的范围 (作) 到最广泛的范围 (全局)。

The ordering for Razor Pages is somewhat simpler, given that you have only two scopes: global scope filters and Razor Page scope filters. For Razor Pages, global scope filters run the Executing and PageHandlerSelected methods first, followed by the page scope filters. For the Executed methods, the filters run in reverse order.
Razor Pages 的排序稍微简单一些,因为你只有两个范围:全局范围筛选器和 Razor Page 范围筛选器。对于 Razor Pages,全局范围筛选器首先运行 Executing 和 PageHandlerSelected 方法,然后运行页面范围筛选器。对于 Executed 方法,筛选器按相反的顺序运行。

You’ll sometimes find you need a bit more control over this order, especially if you have, for example, multiple action filters applied at the same scope. The filter pipeline caters to this requirement by way of the IOrderedFilter interface.
您有时会发现需要对此顺序进行更多控制,尤其是在您在同一范围内应用了多个作筛选器时。筛选器管道通过 IOrderedFilter 接口满足此要求。

21.6.2 Overriding the default order of filter execution with IOrderedFilter

21.6.2 使用 IOrderedFilter 覆盖过滤器执行的默认顺序

Filters are great for extracting cross-cutting concerns from your controller actions and Razor Page, but if you have multiple filters applied to an action, you’ll often need to control the precise order in which they execute.
筛选器非常适合从控制器作和 Razor Page 中提取横切关注点,但如果将多个筛选器应用于一个作,则通常需要控制它们的执行精确顺序。

Scope can get you some of the way, but for those other cases, you can implement IOrderedFilter. This interface consists of a single property, Order:
Scope 可以为您提供一些方法,但对于其他情况,您可以实现 IOrderedFilter。此接口由单个属性 Order 组成:

public interface IOrderedFilter
{
    int Order { get; }
}

You can implement this property in your filters to set the order in which they execute. The filter pipeline orders the filters in each stage based on the Order property first, from lowest to highest, and uses the default scope order to handle ties, as shown in figure 21.5.
您可以在筛选条件中实现此属性,以设置筛选条件的执行顺序。filter 管道首先根据 Order 属性从最低到最高对每个阶段中的过滤器进行排序,并使用默认的作用域 order 来处理平局,如图 21.5 所示。

alt text

Figure 21.5 Controlling the filter order for a stage using the IOrderedFilter interface. Filters are ordered by the Order property first, and then by scope.
图 21.5 使用 IOrderedFilter 接口控制阶段的过滤器顺序。筛选器首先按 Order 属性排序,然后按范围排序。

The filters for Order = -1 execute first, as they have the lowest Order value. The controller filter executes first because it has a broader scope than the action-scope filter. The filters with Order = 0 execute next, in the default scope order, as shown in figure 21.5. Finally, the filter with Order = 1 executes.
首先执行 Order = -1 的筛选器,因为它们具有最低的 Order 值。首先执行 controller 筛选器,因为它的范围比 action-scope 筛选器更广。Order = 0 的 filters 接下来以默认的 scope 顺序执行,如图 21.5 所示。最后,执行 Order = 1 的筛选器。

By default, if a filter doesn’t implement IOrderedFilter, it’s assumed to have Order = 0. All the filters that ship as part of ASP.NET Core have Order = 0, so you can implement your own filters relative to these.
默认情况下,如果筛选器未实现 IOrderedFilter,则假定其 Order = 0。作为 ASP.NET Core 的一部分提供的所有筛选器的 Order = 0,因此您可以实现自己的筛选器。

NOTE You can completely customize how the filter pipeline is built by customizing the MVC frameworks application model conventions. These control everything about how controllers and Razor Pages are discovered, how they’re added to the pipeline, and how filters are discovered. This is an advanced concept, that you won’t often need, but it may occasionally come in handy. You can read about the MVC application model in the documentation at http://mng.bz/nWNa.
注意:您可以通过自定义 MVC 框架应用程序模型约定来完全自定义筛选器管道的构建方式。它们控制有关如何发现控制器和 Razor 页面、如何将它们添加到管道以及如何发现筛选器的所有内容。这是一个高级概念,您通常不需要它,但它偶尔可能会派上用场。您可以在 http://mng.bz/nWNa 的文档 中阅读有关 MVC 应用程序模型的信息。

This chapter has provided a lot of background on the MVC filter pipeline, and we covered most of the technical details you need to use filters and create custom implementations for your own application. In chapter 22 you’ll see some of the built-in filters provided by ASP.NET Core, as well as some practical examples of filters you might want to use in your own applications.
本章提供了许多关于 MVC 过滤器管道的背景知识,我们介绍了使用过滤器和为自己的应用程序创建自定义实现所需的大部分技术细节。在第 22 章中,您将看到 ASP.NET Core 提供的一些内置过滤器,以及您可能希望在自己的应用程序中使用的一些过滤器的实际示例。

21.7 Summary

21.7 总结

The filter pipeline provides hooks into an MVC request so you can run functions at various points within an MVC request. With filters you can run code at specific points in the MVC process across all requests or a subset of requests. This is particularly useful for handling cross-cutting concerns that are specific to MVC.
筛选器管道提供 MVC 请求的挂钩,以便您可以在 MVC 请求中的不同点运行函数。使用筛选器,您可以在 MVC 进程中的特定点跨所有请求或请求子集运行代码。这对于处理特定于 MVC 的横切关注点特别有用。

The filter pipeline executes as part of the MVC or Razor Pages execution. It consists of authorization filters, resource filters, action filters, page filters, exception filters, and result filters. Each filter type is grouped in a stage and can be used to achieve effects specific to that stage.
筛选器管道作为 MVC 或 Razor Pages 执行的一部分执行。它由授权筛选条件、资源筛选条件、作筛选条件、页面筛选条件、异常筛选条件和结果筛选条件组成。每种滤镜类型都分组在一个阶段中,可用于实现特定于该阶段的效果。

Resource, action, and result filters run twice in the pipeline: an Executing method on the way in and an Executed method on the way out. Page filters run three times: after page handler selection, and before and after page handler execution.
资源、作和结果筛选器在管道中运行两次:一个 Executing 方法在流入中,一个 Executed 方法在流出。页面过滤器运行三次:选择页面处理程序之后,以及页面处理程序执行之前和之后。

Authorization and exception filters run only once as part of the pipeline; they don’t run after a response has been generated.
授权和异常筛选器仅作为管道的一部分运行一次;它们在生成响应后不会运行。

Each type of filter has both a sync and an async version. For example, resource filters can implement either the IResourceFilter interface or the IAsync-ResourceFilter interface. You should use the synchronous interface unless your filter needs to use asynchronous method calls.
每种类型的筛选器都有同步版本和异步版本。例如,资源筛选器可以实现 IResourceFilter 接口或 IAsync-ResourceFilter 接口。除非 filter 需要使用异步方法调用,否则应使用 synchronous interface。

You can add filters globally, at the controller level, at the Razor Page level, or at the action level. This is called the scope of the filter. Which scope you should choose depends on how broadly you want to apply the filter.
您可以在控制器级别、Razor Page 级别或作级别全局添加筛选器。这称为筛选器的范围。您应该选择哪个范围取决于您要应用过滤器的范围。

Within a given stage, global-scoped filters run first, then controller-scoped, and finally action-scoped. You can also override the default order by implementing the IOrderedFilter interface. Filters run from lowest to highest Order and use scope to break ties.
在给定的阶段中,全局范围的过滤器首先运行,然后是控制器范围的,最后是作范围的。您还可以通过实现 IOrderedFilter 接口来覆盖默认顺序。筛选器从最低顺序到最高顺序运行,并使用范围来打破关系。

Leave a Reply

Your email address will not be published. Required fields are marked *