22 Creating custom MVC and Razor Page filters
22 创建自定义 MVC 和 Razor 页面筛选器
This chapter covers
本章涵盖
• Creating custom filters to refactor complex action methods
创建自定义筛选器以重构复杂的作方法
• Using authorization filters to protect your action methods and Razor Pages
使用授权筛选器保护作方法和 Razor 页面
• Short-circuiting the filter pipeline to bypass action and page handler execution
使筛选器管道短路以绕过作和页面处理程序执行
• Injecting dependencies into filters
将依赖项注入筛选器
In chapter 21 I introduced the Model-View-Controller (MVC) and Razor Pages filter pipeline and showed where it fits into the life cycle of a request. You learned how to apply filters to your action method, controllers, and Razor Pages, and the effect of scope on the filter execution order.
在第 21 章中,我介绍了模型-视图-控制器 (MVC) 和 Razor Pages 过滤器管道,并展示了它在请求生命周期中的位置。你了解了如何将筛选器应用于作方法、控制器和 Razor Pages,以及范围对筛选器执行顺序的影响。
In this chapter you’ll take that knowledge and apply it to a concrete example. You’ll learn to create custom filters that you can use in your own apps and how to use them to reduce duplicate code in your action methods.
在本章中,您将利用这些知识并将其应用于具体示例。您将学习如何创建可在自己的应用程序中使用的自定义过滤器,以及如何使用它们来减少作方法中的重复代码。
In section 22.1 I take you through the filter types in detail, how they fit into the MVC pipeline, and what to use them for. For each one, I’ll provide example implementations that you might use in your own application and describe the built-in options available.
在 Section 22.1 中,我将向您详细介绍过滤器类型,它们如何适应 MVC 管道,以及使用它们的用途。对于每个选项,我将提供您可以在自己的应用程序中使用的示例实现,并描述可用的内置选项。
A key feature of filters is the ability to short-circuit a request by generating a response and halting progression through the filter pipeline. This is similar to the way short-circuiting works in middleware, but there are subtle differences for MVC filters. On top of that, the exact behavior is slightly different for each filter, and I cover that in section 22.2.
过滤器的一个关键功能是能够通过生成响应并停止通过过滤器管道的进程来短路请求。这类似于中间件中短路的工作方式,但 MVC 筛选器存在细微的差异。最重要的是,每个过滤器的确切行为略有不同,我在 22.2 节中介绍了这一点。
You typically add MVC filters to the pipeline by implementing them as attributes added to your controller classes, action methods, and Razor Pages. Unfortunately, you can’t easily use dependency injection (DI) with attributes due to the limitations of C#. In section 22.3 I show you how to use the ServiceFilterAttribute and TypeFilterAttribute base classes to enable DI in your filters.
通常,通过将 MVC 筛选器实现为添加到控制器类、作方法和 Razor Pages 的属性,将 MVC 筛选器添加到管道中。遗憾的是,由于 C# 的限制,你不能轻松地将依赖项注入 (DI) 与属性一起使用。在第 22.3 节中,我将向您展示如何使用 ServiceFilterAttribute 和 TypeFilterAttribute 基类在过滤器中启用 DI。
We covered all the background for filters in chapter 21, so in the next section we jump straight into the code and start creating custom MVC filters.
我们在第 21 章中介绍了过滤器的所有背景,因此在下一节中,我们将直接进入代码并开始创建自定义 MVC 过滤器。
22.1 Creating custom filters for your application
22.1 为您的应用程序创建自定义过滤器
ASP.NET Core includes several filters that you can use out of the box, but often the most useful filters are the custom ones that are specific to your own apps. In this section we’ll work through each of the six types of filters I covered in chapter 21. I’ll explain in more detail what they’re for and when you should use them. I’ll point out examples of these filters that are part of ASP.NET Core itself, and you’ll see how to create custom filters for an example application.
ASP.NET Core 包含多个开箱即用的筛选器,但通常最有用的筛选器是特定于你自己的应用程序的自定义筛选器。在本节中,我们将介绍我在第 21 章中介绍的六种类型的过滤器中的每一种。我将更详细地解释它们的用途以及何时应该使用它们。我将指出这些筛选器的示例,这些筛选器是 ASP.NET Core 本身的一部分,您将了解如何为示例应用程序创建自定义筛选器。
To give you something realistic to work with, we’ll start with a web API controller for accessing the recipe application from chapter 12. This controller contains two actions: one for fetching a RecipeDetailViewModel and another for updating a Recipe with new values. The following listing shows your starting point for this chapter, including both action methods.
为了给你一些实际的使用,我们将从一个 Web API 控制器开始,用于访问第 12 章中的配方应用程序。此控制器包含两个作:一个用于获取 RecipeDetailViewModel,另一个用于使用新值更新 Recipe。下面的清单显示了本章的起点,包括两种作方法。
Listing 22.1 Recipe web API controller before refactoring to use filters
列表 22.1 重构以使用过滤器之前的配方 Web API 控制器
[Route("api/recipe")]
public class RecipeApiController : ControllerBase
{
private readonly bool IsEnabled = true; #A
public RecipeService _service;
public RecipeApiController(RecipeService service)
{
_service = service;
}
[HttpGet("{id}")]
public IActionResult Get(int id)
{
if (!IsEnabled) { return BadRequest(); } #B
try
{
if (!_service.DoesRecipeExist(id)) #C
{ #C
return NotFound(); #C
} #C
var detail = _service.GetRecipeDetail(id); #D
Response.GetTypedHeaders().LastModified = #E
detail.LastModified; #E
return Ok(detail); #F
}
catch (Exception ex) #G
{ #G
return GetErrorResponse(ex); #G
} #G
}
[HttpPost("{id}")]
public IActionResult Edit(
int id, [FromBody] UpdateRecipeCommand command)
{
if (!IsEnabled) { return BadRequest(); } #H
try
{
if (!ModelState.IsValid) #I
{ #I
return BadRequest(ModelState); #I
} #I
if (!_service.DoesRecipeExist(id)) #J
{ #J
return NotFound(); #J
} #J
_service.UpdateRecipe(command); #K
return Ok(); #K
}
catch (Exception ex) #L
{ #L
return GetErrorResponse(ex); #L
} #L
}
private static IActionResult GetErrorResponse(Exception ex)
{
var error = new ProblemDetails
{
Title = "An error occurred",
Detail = context.Exception.Message,
Status = 500,
Type = "https://httpstatuses.com/500"
};
return new ObjectResult(error)
{
StatusCode = 500
};
}
}
❶ This field would be passed in as configuration and is used to control access to actions.
此字段将作为配置传入,用于控制对作的访问。
❷ If the API isn’t enabled, blocks further execution
如果未启用 API,则阻止进一步执行
❸ If the requested Recipe doesn’t exist, returns a 404 response
如果请求的配方不存在,则返回 404 响应
❹ Fetches RecipeDetailViewModel
获取 RecipeDetailViewModel
❺ Sets the Last-Modified response header to the value in the model
将 Last-Modified 响应标头设置为模型中的值
❻ Returns the view model with a 200 response
返回响应为 200 的视图模型
❼ If an exception occurs, catches it and returns the error in an expected format, as a 500 error
如果发生异常,则捕获它并以预期的格式返回错误, 作为 500 错误
❽ If the API isn’t enabled, blocks further execution
如果未启用 API,则阻止进一步执行
❾ Validates the binding model and returns a 400 response if there are errors
验证绑定模型并在出现错误时返回 400 响应
❿ If the requested Recipe doesn’t exist, returns a 404 response
如果请求的配方不存在,则返回 404 响应
⓫ Updates the Recipe from the command and returns a 200 response
从命令更新配方并返回 200 响应
⓬ If an exception occurs, catches it and returns the error in an expected format, as a 500 error
如果发生异常,则捕获它并以预期的格式返回错误, 作为 500 错误
These action methods currently have a lot of code to them, which hides the intent of each action. There’s also quite a lot of duplication between the methods, such as checking that the Recipe entity exists and formatting exceptions.
这些 action method 目前有很多代码,这隐藏了每个 action 的意图。方法之间也存在相当多的重复,例如检查 Recipe 实体是否存在和格式化异常。
In this section you’re going to refactor this controller to use filters for all the code in the methods that’s unrelated to the intent of each action. By the end of the chapter you’ll have a much simpler controller controller that’s far easier to understand, as shown here.
在本节中,您将重构此控制器,以便对方法中与每个作的意图无关的所有代码使用过滤器。在本章结束时,您将拥有一个更简单的控制器控制器,它更容易理解,如下所示。
Listing 22.2 Recipe web API controller after refactoring to use filters
列表 22.2 重构为使用过滤器后的配方 Web API 控制器
[Route("api/recipe")]
[ValidateModel] #A
[HandleException] #A
[FeatureEnabled(IsEnabled = true)] #A
public class RecipeApiController : ControllerBase
{
public RecipeService _service;
public RecipeApiController(RecipeService service)
{
_service = service;
}
[HttpGet("{id}")]
[EnsureRecipeExists] #B
[AddLastModifiedHeader] #B
public IActionResult Get(int id)
{
var detail = _service.GetRecipeDetail(id); #C
return Ok(detail); #C
}
[HttpPost("{id}")]
[EnsureRecipeExists] #D
public IActionResult Edit(
int id, [FromBody] UpdateRecipeCommand command)
{
_service.UpdateRecipe(command); #E
return Ok(); #E
}
}
❶ The filters encapsulate the majority of logic common to multiple action methods.
过滤器封装了多个方法通用的大多数逻辑。
❷ Placing filters at the action level limits them to a single action.
将过滤器放在作级别会将它们限制为单个作。
❸ The intent of the action, return a Recipe view model, is much clearer.
作的意图(返回配方视图模型)要清晰得多。
❹ Placing filters at the action level can control the order in which they execute.
将过滤器放在作级别可以控制它们的执行顺序。
❺ The intent of the action, update a Recipe, is much clearer.
作 update a Recipe 的意图要明确得多。
I think you’ll have to agree that the controller in listing 22.2 is much easier to read! In this section you’ll refactor the controller bit by bit, removing cross-cutting code to get to something more manageable. All the filters we’ll create in this section will use the sync filter interfaces. I’ll leave it to you, as an exercise, to create their async counterparts. We’ll start by looking at authorization filters and how they relate to security in ASP.NET Core.
我想你得同意,清单 22.2 中的控制器要容易得多!在本节中,您将逐步重构控制器,删除横切代码以获得更易于管理的内容。我们将在本节中创建的所有过滤器都将使用 sync 过滤器接口。作为练习,我将留给您创建它们的异步对应项。首先,我们将了解授权筛选器以及它们与 ASP.NET Core 中的安全性有何关系。
22.1.1 Authorization filters: Protecting your APIs
22.1.1 授权过滤器:保护您的 API
Authentication and authorization are related, fundamental concepts in security that we’ll be looking at in detail in chapters 23 and 24.
身份验证和授权是安全中相关的基本概念,我们将在第 23 章和第 24 章中详细介绍。
DEFINITION Authentication is concerned with determining who made a request. Authorization is concerned with what a user is allowed to access.
定义:身份验证与确定谁发出了请求有关。授权与允许用户访问的内容有关。
Authorization filters run first in the MVC filter pipeline, before any other filters. They control access to the action method by immediately short-circuiting the pipeline when a request doesn’t meet the necessary requirements.
授权筛选器首先在 MVC 筛选器管道中运行,然后再运行任何其他筛选器。当请求不满足必要的要求时,它们通过立即使管道短路来控制对作方法的访问。
ASP.NET Core has a built-in authorization framework that you should use when you need to protect your MVC application or your web APIs. You can configure this framework with custom policies that let you finely control access to your actions.
ASP.NET Core 有一个内置的授权框架,当您需要保护 MVC 应用程序或 Web API 时,您应该使用该框架。您可以使用自定义策略配置此框架,以便精细控制对作的访问。
Tip It’s possible to write your own authorization filters by implementing IAuthorizationFilter or IAsyncAuthorizationFilter, but I strongly advise against it. The ASP.NET Core authorization framework is highly configurable and should meet all your needs.
提示:通过实现 IAuthorizationFilter 或 IAsyncAuthorizationFilter 可以编写自己的授权筛选器,但我强烈建议不要这样做。ASP.NET Core 授权框架是高度可配置的,应该可以满足您的所有需求。
At the heart of MVC authorization is an authorization filter, AuthorizeFilter, which you can add to the filter pipeline by decorating your actions or controllers with the [Authorize] attribute. In its simplest form, adding the [Authorize] attribute to an action, as in the following listing, means that the request must be made by an authenticated user to be allowed to continue. If you’re not logged in, it will short-circuit the pipeline, returning a 401 Unauthorized response to the browser.
MVC 授权的核心是授权筛选器 AuthorizeFilter,您可以通过使用 [Authorize] 属性修饰作或控制器来将其添加到筛选器管道中。在最简单的形式中,将 [Authorize] 属性添加到作中,如下面的清单所示,意味着请求必须由经过身份验证的用户发出才能继续。如果您未登录,它将使管道短路,向浏览器返回 401 Unauthorized 响应。
Listing 22.3 Adding [Authorize] to an action method
清单 22.3 向作方法添加 [Authorize]
public class RecipeApiController : ControllerBase
{
public IActionResult Get(int id) #A
{
// method body
}
[Authorize] #B
public IActionResult Edit( #C
int id, [FromBody] UpdateRecipeCommand command) #C
{
// method body
}
}
❶ The Get method has no [Authorize] attribute, so it can be executed by anyone.
Get 方法没有 [Authorize] 属性,因此任何人都可以执行它。
❷ Adds the AuthorizeFilter to the filter pipeline using [Authorize]
使用 [Authorize]将 AuthorizeFilter 添加到筛选器管道
❸ The Edit method can be executed only if you’re logged in.
只有在您登录后才能执行 Edit 方法。
As with all filters, you can apply the [Authorize] attribute at the controller level to protect all the actions on a controller, to a Razor Page to protect all the page handler methods in a page, or even globally to protect every endpoint in your app.
与所有筛选器一样,可以在控制器级别应用 [Authorize] 属性以保护控制器上的所有作,将 [Authorize] 属性应用于 Razor 页面以保护页面中的所有页面处理程序方法,甚至可以全局应用以保护应用中的每个终结点。
NOTE We’ll explore authorization in detail in chapter 24, including how to add more detailed requirements so that only specific sets of users can execute an action.
注意:我们将在第 24 章中详细探讨授权,包括如何添加更详细的要求,以便只有特定的用户集才能执行作。
The next filters in the pipeline are resource filters. In the next section you’ll extract some of the common code from RecipeApiController and see how easy it is to create a short-circuiting filter.
管道中的下一个筛选器是资源筛选器。在下一节中,您将从 RecipeApiController 中提取一些通用代码,并了解创建短路过滤器是多么容易。
22.1.2 Resource filters: Short-circuiting your action methods
22.1.2 资源过滤器:使作方法短路
Resource filters are the first general-purpose filters in the MVC filter pipeline. In chapter 21 you saw minimal examples of both sync and async resource filters, which logged to the console. In your own apps, you can use resource filters for a wide range of purposes, thanks to the fact that they execute so early (and late) in the filter pipeline.
资源筛选器是 MVC 筛选器管道中的第一个通用筛选器。在第 21 章中,您看到了 sync 和 async 资源过滤器的最小示例,它们记录到控制台中。在您自己的应用程序中,您可以将资源筛选条件用于多种用途,这要归功于它们在筛选管道中执行得如此早(和延迟)的事实。
The ASP.NET Core framework includes a few implementations of resource filters you can use in your apps:
ASP.NET Core 框架包含一些可在应用中使用的资源筛选器实现:
• ConsumesAttribute—Can be used to restrict the allowed formats an action method can accept. If your action is decorated with [Consumes("application/json")], but the client sends the request as Extensible Markup Language (XML), the resource filter will short-circuit the pipeline and return a 415 Unsupported Media Type response.
ConsumesAttribute - 可用于限制作方法可以接受的允许格式。如果您的作使用 [Consumes(“application/json”)] 修饰,但客户端以可扩展标记语言 (XML) 的形式发送请求,则资源筛选器将使管道短路并返回 415 不支持的媒体类型响应。
• SkipStatusCodePagesAttribute—This filter prevents the StatusCodePagesMiddleware from running for the response. This can be useful if, for example, you have both web API controllers and Razor Pages in the same application. You can apply this attribute to the controllers to ensure that API error responses are passed untouched, but all error responses from Razor Pages are handled by the middleware.
SkipStatusCodePagesAttribute - 此筛选器可防止 StatusCodePagesMiddleware 针对响应运行。例如,如果同一应用程序中同时具有 Web API 控制器和 Razor Pages,这可能非常有用。可以将此属性应用于控制器,以确保 API 错误响应原封不动地传递,但来自 Razor Pages 的所有错误响应都由中间件处理。
Resource filters are useful when you want to ensure that the filter runs early in the pipeline, before model binding. They provide an early hook into the pipeline for your logic so you can quickly short-circuit the request if you need to.
当您想要确保筛选条件在模型绑定之前在管道中尽早运行时,资源筛选条件非常有用。它们为您的 logic 提供了管道的早期钩子,因此您可以在需要时快速短路请求。
Look back at listing 22.1 and see whether you can refactor any of the code into a resource filter. One candidate line appears at the start of both the Get and Edit methods:
回顾一下清单 22.1,看看你是否可以将任何代码重构为资源过滤器。一个候选行出现在 Get 和 Edit 方法的开头:
if (!IsEnabled) { return BadRequest(); }
This line of code is a feature toggle that you can use to disable the availability of the whole API, based on the IsEnabled field. In practice, you’d probably load the IsEnabled field from a database or configuration file so you could control the availability dynamically at runtime, but for this example I’m using a hardcoded value.
这行代码是一个功能切换,可用于根据 IsEnabled 字段禁用整个 API 的可用性。在实践中,您可能会从数据库或配置文件加载 IsEnabled 字段,以便您可以在运行时动态控制可用性,但对于此示例,我使用的是硬编码值。
Tip To read more about using feature toggles in your applications, see my series “Adding feature flags to an ASP.NET Core app” at http://mng.bz/2e40.
提示:要了解有关在应用程序中使用功能切换的更多信息,请参阅我在 http://mng.bz/2e40 上的系列文章“向 ASP.NET Core 应用程序添加功能标志”。
This piece of code is self-contained cross-cutting logic, which is somewhat orthogonal to the main intent of each action method—a perfect candidate for a filter. You want to execute the feature toggle early in the pipeline, before any other logic, so a resource filter makes sense.
这段代码是自包含的横切逻辑,它与每个作方法的主要意图在某种程度上正交,是过滤器的完美候选者。您希望在管道的早期、任何其他逻辑之前执行功能切换,因此资源筛选条件是有意义的。
Tip Technically, you could also use an authorization filter for this example, but I’m following my own advice of “Don’t write your own authorization filters!”
提示:从技术上讲,您也可以对此示例使用授权过滤器,但我遵循我自己的建议,即“不要编写自己的授权过滤器!
The next listing shows an implementation of FeatureEnabledAttribute, which extracts the logic from the action methods and moves it into the filter. I’ve also exposed the IsEnabled field as a property on the filter.
下一个清单显示了 FeatureEnabledAttribute 的实现,它从作方法中提取逻辑并将其移动到过滤器中。我还将 IsEnabled 字段作为筛选器上的属性公开。
Listing 22.4 The FeatureEnabledAttribute resource filter
列表 22.4 FeatureEnabledAttribute 资源过滤器
public class FeatureEnabledAttribute : Attribute, IResourceFilter
{
public bool IsEnabled { get; set; } #A
public void OnResourceExecuting( #B
ResourceExecutingContext context) #B
{
if (!IsEnabled) #C
{ #C
context.Result = new BadRequestResult(); #C
} #C
}
public void OnResourceExecuted( #D
ResourceExecutedContext context) { } #D
}
❶ Defines whether the feature is enabled
定义是否启用该功能
❷ Executes before model binding, early in the filter pipeline
在模型绑定之前执行,在过滤器管道的早期
❸ If the feature isn’t enabled, short-circuits the pipeline by setting the context.Result property
如果未启用该功能,则通过设置上下文来短路管道。结果属性
❹ Must be implemented to satisfy IResourceFilter, but not needed in this case
必须实现以满足 IResourceFilter,但在这种情况下不需要
This simple resource filter demonstrates a few important concepts, which are applicable to most filter types:
这个简单的资源筛选条件演示了几个重要的概念,这些概念适用于大多数筛选条件类型:
• The filter is an attribute as well as a filter. This lets you decorate your controller, action methods, and Razor Pages with it using [FeatureEnabled(IsEnabled = true)].
过滤器既是属性又是过滤器。这样,您就可以使用 [FeatureEnabled(IsEnabled = true)] 使用它来装饰控制器、作方法和 Razor 页面。
• The filter interface consists of two methods: Executing, which runs before model binding, and Executed, which runs after the result has executed. You must implement both, even if you only need one for your use case.
筛选器接口由两种方法组成:Executing (在模型绑定之前运行)和 Executed (在结果执行之后运行)。您必须同时实施这两个项目,即使您的用例只需要一个项目。
• The filter execution methods provide a context object. This provides access to, among other things, the HttpContext for the request and metadata about the action method that was selected.
过滤器执行方法提供上下文对象。这提供了对请求的 HttpContext 和有关所选作方法的元数据等的访问。
• To short-circuit the pipeline, set the context.Result property to an IactionResult instance. The framework will execute this result to generate the response, bypassing any remaining filters in the pipeline and skipping the action method (or page handler) entirely. In this example, if the feature isn’t enabled, you bypass the pipeline by returning BadRequestResult, which returns a 400 error to the client.
要使管道短路,请设置上下文。Result 属性设置为 IactionResult 实例。框架将执行此结果以生成响应,绕过管道中的任何剩余筛选器,并完全跳过作方法(或页面处理程序)。在此示例中,如果未启用该功能,则通过返回 BadRequestResult 来绕过管道,这会向客户端返回 400 错误。
By moving this logic into the resource filter, you can remove it from your action methods and instead decorate the whole API controller with a simple attribute:
通过将此逻辑移动到资源过滤器中,您可以将其从 action 方法中删除,而是使用 simple 属性装饰整个 API 控制器:
[FeatureEnabled(IsEnabled = true)]
[Route("api/recipe")]
public class RecipeApiController : ControllerBase
You’ve extracted only two lines of code from your action methods so far, but you’re on the right track. In the next section we’ll move on to action filters and extract two more filters from the action method code.
到目前为止,您只从 action 方法中提取了两行代码,但您走在正确的轨道上。在下一节中,我们将继续讨论作筛选器,并从作方法代码中提取另外两个筛选器。
22.1.3 Action filters: Customizing model binding and action results
22.1.3作过滤器:自定义模型绑定和作结果
Action filters run just after model binding, before the action method executes. Thanks to this positioning, action filters can access all the arguments that will be used to execute the action method, which makes them a powerful way of extracting common logic out of your actions.
Action筛选器在模型绑定之后,在作方法执行之前运行。由于这种定位,动作过滤器可以访问将用于执行动作方法的所有参数,这使它们成为从动作中提取通用逻辑的强大方式。
On top of this, they run after the action method has executed and can completely change or replace the IActionResult returned by the action if you want. They can even handle exceptions thrown in the action.
最重要的是,它们在作方法执行后运行,并且可以根据需要完全更改或替换作返回的 IActionResult。它们甚至可以处理作中引发的异常。
NOTE Action filters don’t execute for Razor Pages. Similarly, page filters don’t execute for action methods.
注意:不会对 Razor Pages 执行作筛选器。同样,页面过滤器不会对作方法执行。
The ASP.NET Core framework includes several action filters out of the box. One of these commonly used filters is ResponseCacheFilter, which sets HTTP caching headers on your action-method responses.
ASP.NET Core 框架包含多个现成的作筛选器。其中一个常用的过滤器是 ResponseCacheFilter,它在作方法响应上设置 HTTP 缓存标头。
NOTE I have described filters as being attributes, but that’s not always the case. For example, the action filter is called ResponseCacheFilter, but this type is internal to the ASP.NET Core framework. To apply the filter, you use the public [ResponseCache] attribute instead, and the framework automatically configures the ResponseCacheFilter as appropriate. This separation between attribute and filter is largely an artifact of the internal design, but it can be useful, as you’ll see in section 22.3.
注意:我曾将过滤器描述为属性,但情况并非总是如此。例如,作筛选器称为 ResponseCacheFilter,但此类型是 ASP.NET Core 框架的内部类型。若要应用筛选器,请改用公共 [ResponseCache] 属性,框架会根据需要自动配置 ResponseCacheFilter。attribute 和 filter 之间的这种分离在很大程度上是内部设计的产物,但它可能很有用,正如您将在 Section 22.3 中看到的那样。
Response caching vs. output caching
响应缓存与输出缓存Caching is a broad topic that aims to improve the performance of an application over the naive approach. But caching can also make debugging issues difficult and may even be undesirable in some situations. Consequently, I often apply ResponseCacheFilter to my action methods to set HTTP caching headers that disable caching! You can read about this and other approaches to caching in Microsoft’s “Response caching in ASP.NET Core” documentation at http://mng.bz/2eGd.
缓存是一个广泛的主题,旨在通过简单的方法提高应用程序的性能。但是,缓存也会使调试问题变得困难,在某些情况下甚至可能是不可取的。因此,我经常将 ResponseCacheFilter 应用于我的作方法,以设置禁用缓存的 HTTP 缓存标头!您可以在 http://mng.bz/2eGd 的 Microsoft 的“ASP.NET Core 中的响应缓存”文档中阅读有关此方法和其他缓存方法的信息。Note that the ResponseCacheFilter applies cache control headers only to your outgoing responses; it doesn’t cache the response on the server. These headers tell the client (such as a browser) whether it can skip sending a request and reuse the response. If you have relatively static endpoints, this can massively reduce the load on your app.
请注意,ResponseCacheFilter 仅将缓存控制标头应用于您的传出响应;它不会在服务器上缓存响应。这些标头告诉客户端(例如浏览器)是否可以跳过发送请求并重用响应。如果您有相对静态的终端节点,这可以大大减少应用程序的负载。This is different from output caching, introduced in .NET 7. Output caching involves storing a generated response on the server and reusing it for subsequent requests. In the simplest case, the response is stored in memory and reused for appropriate requests, but you can configure ASP.NET Core to store the output elsewhere, such as a database.
这与 .NET 7 中引入的输出缓存不同。输出缓存涉及将生成的响应存储在服务器上,并将其重新用于后续请求。在最简单的情况下,响应存储在内存中并重复用于适当的请求,但您可以将 ASP.NET Core 配置为将输出存储在其他位置,例如数据库。Output caching is generally more configurable than response caching, as you can choose exactly what to cache and when to invalidate it, but it is also much more resource-heavy. For details on how to enable output caching for an endpoint, see the documentation at http://mng.bz/Bmlv.
输出缓存通常比响应缓存更易于配置,因为您可以准确选择要缓存的内容以及何时使其失效,但它的资源消耗也要大得多。有关如何为终端节点启用输出缓存的详细信息,请参阅 http://mng.bz/Bmlv 中的文档。
The real power of action filters comes when you build filters tailored to your own apps by extracting common code from your action methods. To demonstrate, I’m going to create two custom filters for RecipeApiController:
当您通过从作方法中提取通用代码来构建针对自己的应用程序量身定制的过滤器时,作过滤器的真正强大之处在于。为了演示,我将为 RecipeApiController 创建两个自定义过滤器:
• ValidateModelAttribute—This will return BadRequestResult if the model state indicates that the binding model is invalid and will short-circuit the action execution. This attribute used to be a staple of my web API applications, but the [ApiController] attribute now handles this (and more) for you. Nevertheless, I think it’s useful to understand what’s going on behind the scenes.
ValidateModelAttribute - 如果模型状态指示绑定模型无效,并且将使作执行短路,则将返回 BadRequestResult。此属性曾经是我的 Web API 应用程序的主要内容,但 [ApiController] 属性现在为您处理此 (以及更多) 。尽管如此,我认为了解幕后发生的事情是有用的。
• EnsureRecipeExistsAttribute—This uses each action method’s id argument to validate that the requested Recipe entity exists before the action method runs. If the Recipe doesn’t exist, the filter returns NotFoundResult and short-circuits the pipeline.
EnsureRecipeExistsAttribute - 在作方法运行之前,使用每个作方法的 id 参数来验证请求的配方实体是否存在。如果 Recipe 不存在,则筛选条件将返回 NotFoundResult 并使管道短路。
As you saw in chapter 16, the MVC framework automatically validates your binding models before executing your actions and Razor Page handlers, but it’s up to you to decide what to do about it. For web API controllers, it’s common to return a 400 Bad Request response containing a list of the errors, as shown in figure 22.1.
正如您在第 16 章中所看到的,MVC 框架会在执行您的作和 Razor Page 处理程序之前自动验证您的绑定模型,但由您决定如何处理它。对于 Web API 控制器,通常会返回包含错误列表的 400 Bad Request 响应,如图 22.1 所示。
Figure 22.1 Posting data to a web API using Postman. The data is bound to the action method’s binding model and validated. If validation fails, it’s common to return a 400 Bad Request response with a list of the validation errors.
图 22.1 使用 Postman 将数据发布到 Web API。数据将绑定到作方法的绑定模型并进行验证。如果验证失败,通常会返回 400 Bad Request 响应,其中包含验证错误列表。
You should ordinarily use the [ApiController] attribute on your web API controllers, which gives you this behavior (and uses Problem Details responses) automatically. But if you can’t or don’t want to use that attribute, you can create a custom action filter instead. The following listing shows a basic implementation that is similar to the behavior you get with the [ApiController] attribute.
通常应在 Web API 控制器上使用 [ApiController] 属性,该属性会自动提供此行为 (并使用问题详细信息响应) 。但是,如果您不能或不想使用该属性,则可以改为创建自定义作筛选条件。下面的列表显示了一个基本实现,它类似于您使用 [ApiController] 属性获得的行为。
Listing 22.5 The action filter for validating ModelState
列表 22.5 用于验证 ModelState 的动作过滤器
public class ValidateModelAttribute : ActionFilterAttribute #A
{
public override void OnActionExecuting( #B
ActionExecutingContext context) #B
{
if (!context.ModelState.IsValid) #C
{
context.Result = #D
new BadRequestObjectResult(context.ModelState); #D
}
}
}
❶ For convenience, you derive from the ActionFilterAttribute base class.
为方便起见,您可以从 ActionFilterAttribute 基类派生。
❷ Overrides the Executing method to run the filter before the Action executes
重写 Executing 方法以在 Action 执行之前运行过滤器
❸ Model binding and validation have already run at this point, so you can check the state.
此时模型绑定和验证已经运行,因此您可以检查状态。
❹ If the model isn’t valid, sets the Result property, which short-circuits the action execution
如果模型无效,则设置 Result 属性,这将使作执行短路
This attribute is self-explanatory and follows a similar pattern to the resource filter in section 22.1.2, but with a few interesting points:
此属性是不言自明的,并且遵循与第 22.1.2 节中的资源过滤器类似的模式,但有一些有趣的点:
• I have derived from the abstract ActionFilterAttribute. This class implements IActionFilter and IResultFilter, as well as their async counterparts, so you can override the methods you need as appropriate. This prevents needing to add an unused OnActionExecuted() method, but using the base class is entirely optional and a matter of preference.
我从抽象的 ActionFilterAttribute 派生而来。此类实现 IActionFilter 和 IResultFilter 以及它们的异步对应项,因此您可以根据需要重写所需的方法。这样就不需要添加未使用的 OnActionExecuted() 方法,但使用基类完全是可选的,并且是首选项问题。
• Action filters run after model binding has taken place, so context.ModelState contains the validation errors if validation failed.
Action筛选器在模型绑定发生后运行,因此 context.ModelState 包含验证错误(如果验证失败)。
• Setting the Result property on context short-circuits the pipeline. But due to the position of the action filter stage, only the action method execution and later action filters are bypassed; all the other stages of the pipeline run as though the action executed as normal.
在上下文上设置 Result 属性会使管道短路。但是,由于作筛选器阶段的位置,仅绕过作方法执行和以后的作筛选器;管道的所有其他阶段都像正常执行作一样运行。
If you apply this action filter to your RecipeApiController, you can remove this code from the start of both the action methods, as it will run automatically in the filter pipeline:
如果你将此作筛选器应用于 RecipeApiController,则可以从两个作方法的开头删除此代码,因为它将在筛选器管道中自动运行:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
You’ll use a similar approach to remove the duplicate code that checks whether the id provided as an argument to the action methods corresponds to an existing Recipe entity.
您将使用类似的方法来删除重复代码,该代码检查作为作方法的参数提供的 id 是否对应于现有 Recipe 实体。
The following listing shows the EnsureRecipeExistsAttribute action filter. This uses an instance of RecipeService to check whether the Recipe exists and returns a 404 Not Found if it doesn’t.
以下清单显示了 EnsureRecipeExistsAttribute作筛选器。这将使用 RecipeService 的实例来检查 Recipe 是否存在,如果不存在,则返回 404 Not Found。
Listing 22.6 An action filter to check whether a Recipe exists
清单 22.6 用于检查 Recipe 是否存在的 action filter
public class EnsureRecipeExistsAtribute : ActionFilterAttribute
{
public override void OnActionExecuting(
ActionExecutingContext context)
{
var service = context.HttpContext.RequestServices #A
.GetService<RecipeService>(); #A
var recipeId = (int) context.ActionArguments["id"]; #B
if (!service.DoesRecipeExist(recipeId)) #C
{
context.Result = new NotFoundResult(); #D
}
}
}
❶ Fetches an instance of RecipeService from the DI container
从 DI 容器中获取 RecipeService 的实例
❷ Retrieves the id parameter that will be passed to the action method when it executes
检索执行时将传递给作方法的 id 参数
❸ Checks whether a Recipe entity with the given RecipeId exists
检查具有给定 RecipeId 的 Recipe 实体是否存在
❹ If it doesn’t exist, returns a 404 Not Found result and short-circuits the pipeline
如果不存在,则返回 404 Not Found 结果并短路管道
As before, you’ve derived from ActionFilterAttribute for simplicity and overridden the OnActionExecuting method. The main functionality of the filter relies on the DoesRecipeExist() method of RecipeService, so the first step is to obtain an instance of RecipeService. The context parameter provides access to the HttpContext for the request, which in turn lets you access the DI container and use RequestServices.GetService() to return an instance of RecipeService.
与以前一样,为了简单起见,您从 ActionFilterAttribute 派生并重写了 OnActionExecuting 方法。过滤器的主要功能依赖于 RecipeService 的 DoesRecipeExist() 方法,因此第一步是获取 RecipeService 的实例。context 参数提供对请求的 HttpContext 的访问,这反过来又允许您访问 DI 容器并使用 RequestServices.GetService() 返回 RecipeService 的实例。
Warning This technique for obtaining dependencies is known as service location and is generally considered to be an antipattern. In section 22.3 I’ll show you a better way to use the DI container to inject dependencies into your filters.
警告:这种用于获取依赖项的技术称为 服务定位,通常被视为反模式。在 Section 22.3 中,我将向您展示一种更好的方法 使用 DI 容器将依赖项注入过滤器。
As well as RecipeService, the other piece of information you need is the id argument of the Get and Edit action methods. In action filters, model binding has already occurred, so the arguments that the framework will use to execute the action method are already known and are exposed on context.ActionArguments.
除了 RecipeService 之外,您还需要的另一条信息是 Get 和 Edit作方法的 id 参数。在作筛选器中,模型绑定已经发生,因此框架将用于执行作方法的参数是已知的,并在上下文中公开。ActionArguments 的 API 参数。
The action arguments are exposed as Dictionary<string, object>
, so you can obtain the id parameter using the "id" string key. Remember to cast the object to the correct type.
action参数公开为 Dictionary<string, object>
,因此您可以使用 “id” 字符串键获取 id 参数。请记住将对象强制转换为正确的类型。
Tip Whenever I see magic strings like this, I always try to replace them by using the nameof operator. Unfortunately, nameof won’t work for method arguments like this, so be careful when refactoring your code. I suggest explicitly applying the action filter to the action method (instead of globally, or to a controller) to remind you about that implicit coupling.
提示:每当我看到这样的魔术字符串时,我总是尝试使用 nameof 运算符替换它们。不幸的是,nameof 不适用于这样的方法参数,因此在重构代码时要小心。我建议将 action 过滤器显式地应用于 action 方法(而不是全局应用,或应用于控制器),以提醒您这种隐式耦合。
With RecipeService and id in place, it’s a case of checking whether the identifier corresponds to an existing Recipe entity and if not, setting context.Result to NotFoundResult. This short-circuits the pipeline and bypasses the action method altogether.
有了 RecipeService 和 id,就可以检查标识符是否对应于现有的 Recipe 实体,如果不是,则设置 context。Result 设置为 NotFoundResult。这会使管道短路并完全绕过 action 方法。
NOTE Remember that you can have multiple action filters running in a single stage. Short-circuiting the pipeline by setting context.Result prevents later filters in the stage from running and bypasses the action method execution.
注意:请记住,您可以在单个阶段中运行多个作筛选器。通过设置 context 使管道短路。Result 会阻止阶段中以后的过滤器运行,并绕过作方法的执行。
Before we move on, it’s worth mentioning a special case for action filters. The ControllerBase base class implements IActionFilter and IAsyncActionFilter itself. If you find yourself creating an action filter for a single controller and want to apply it to every action in that controller, you can override the appropriate methods on your controller instead, as in the following listing.
在我们继续之前,值得一提的是作过滤器的一个特殊情况。ControllerBase 基类实现 IActionFilter 和 IAsyncActionFilter 本身。如果您发现自己为单个控制器创建了一个作过滤器,并希望将其应用于该控制器中的每个作,则可以改为覆盖控制器上的相应方法,如下面的清单所示。
Listing 22.7 Overriding action filter methods directly on ControllerBase
清单 22.7 直接在 ControllerBase 上覆盖动作过滤器方法
public class HomeController : ControllerBase #A
{
public override void OnActionExecuting( #B
ActionExecutingContext context) #B
{ } #B
public override void OnActionExecuted( #C
ActionExecutedContext context) #C
{ } #C
}
❶ Derives from the ControllerBase class
派生自 ControllerBase 类
❷ Runs before any other action filters for every action in the controller
在控制器中每个动作的任何其他动作过滤器之前运行
❸ Runs after all other action filters for every action in the controller
在控制器中每个动作的所有其他动作过滤器之后运行
If you override these methods on your controller, they’ll run in the action filter stage of the filter pipeline for every action on the controller. The OnActionExecuting method runs before any other action filters, regardless of ordering or scope, and the OnActionExecuted method runs after all other action filters.
如果您在控制器上覆盖这些方法,则它们将在控制器上每个作的过滤器管道的作过滤器阶段中运行。OnActionExecuting 方法在任何其他作筛选器之前运行,而不管顺序或范围如何,而 OnActionExecuted 方法在所有其他作筛选器之后运行。
Tip The controller implementation can be useful in some cases, but you can’t control the ordering related to other filters. Personally, I generally prefer to break logic into explicit, declarative filter attributes, but it depends on the situation, and as always, the choice is yours.
提示:控制器实现在某些情况下可能很有用,但您无法控制与其他过滤器相关的 Sequences。就个人而言,我通常更喜欢将 logic 分解为显式的声明性 filter 属性,但这取决于具体情况,并且一如既往,选择权在您手中。
With the resource and action filters complete, your controller is looking much tidier, but there’s one aspect in particular that would be nice to remove: the exception handling. In the next section we’ll look at how to create a custom exception filter for your controller and why you might want to do this instead of using exception handling middleware.
完成资源和作筛选器后,您的控制器看起来更加整洁,但有一个方面特别值得删除:异常处理。在下一节中,我们将了解如何为控制器创建自定义异常筛选器,以及为什么您可能希望执行此作而不是使用异常处理中间件。
22.1.4 Exception filters: Custom exception handling for your action methods
22.1.4 异常过滤器:作方法的自定义异常处理
In chapter 4 I went into some depth about types of error-handling middleware you can add to your apps. These let you catch exceptions thrown from any later middleware and handle them appropriately. If you’re using exception handling middleware, you may be wondering why we need exception filters at all.
在第 4 章中,我深入探讨了您可以添加到应用程序中的错误处理中间件的类型。这些允许您捕获任何后续中间件引发的异常并适当地处理它们。如果你正在使用异常处理中间件,你可能想知道为什么我们需要异常过滤器。
The answer to this is pretty much the same as I outlined in chapter 21: filters are great for cross-cutting concerns, when you need behavior that’s specific to MVC or that should only apply to certain routes.
这个问题的答案与我在第 21 章中概述的几乎相同:当您需要特定于 MVC 的行为或应仅适用于某些路由的行为时,过滤器非常适合横切关注点。
Both of these can apply in exception handling. Exception filters are part of the MVC framework, so they have access to the context in which the error occurred, such as the action or Razor Page that was executing. This can be useful for logging additional details when errors occur, such as the action parameters that caused the error.
这两者都可以应用于异常处理。异常筛选器是 MVC 框架的一部分,因此它们有权访问发生错误的上下文,例如正在执行的作或 Razor Page。这对于在发生错误时记录其他详细信息(如导致错误的作参数)非常有用。
Warning If you use exception filters to record action method arguments, make sure you’re not storing sensitive data in your logs, such as passwords or credit card details.
警告:如果您使用异常筛选条件来记录作方法参数,请确保您没有在日志中存储敏感数据,例如密码或信用卡详细信息。
You can also use exception filters to handle errors from different routes in different ways. Imagine you have both Razor Pages and web API controllers in your app, as we do in the recipe app. What happens when an exception is thrown by a Razor Page?
您还可以使用异常筛选条件以不同方式处理来自不同路由的错误。假设您的应用程序中同时有 Razor Pages 和 Web API 控制器,就像我们在配方应用程序中所做的那样。当 Razor Page 引发异常时会发生什么情况?
As you saw in chapter 4, the exception travels back up the middleware pipeline and is caught by exception handler middleware. The exception handler middleware reexecutes the pipeline and generates an HTML error page.
正如您在第 4 章中看到的,异常沿中间件管道向上传输,并被异常处理程序中间件捕获。异常处理程序中间件将重新执行管道并生成 HTML 错误页。
That’s great for your Razor Pages, but what about exceptions in your web API controllers? If your API throws an exception and consequently returns HTML generated by the exception handler middleware, that’s going to break a client that called the API expecting a JavaScript Object Notation (JSON) response!
这对 Razor Pages 来说非常有用,但 Web API 控制器中的异常呢?如果您的 API 引发异常,并因此返回异常处理程序中间件生成的 HTML,这将破坏调用 API 的客户端,该客户端需要 JavaScript 对象表示法 (JSON) 响应!
Tip The added complexity introduced by having to handle these two very different clients is the reason I prefer to create separate applications for APIs and server-rendered apps.
提示:必须处理这两个截然不同的客户端所带来的复杂性增加了,这就是我更喜欢为 API 和服务器呈现的应用程序创建单独的应用程序的原因。
Instead, exception filters let you handle the exception in the filter pipeline and generate an appropriate response body for API clients. The exception handler middleware intercepts only errors without a body, so it will let the modified web API response pass untouched.
相反,异常筛选条件允许您处理筛选条件管道中的异常,并为 API 客户端生成适当的响应正文。异常处理程序中间件仅拦截没有正文的错误,因此它将允许修改后的 Web API 响应原封不动地通过。
NOTE The [ApiController] attribute converts error StatusCodeResults to a ProblemDetails object, but it doesn’t catch exceptions.
注意:[ApiController] 属性将错误 StatusCodeResults 转换为 ProblemDetails 对象,但它不会捕获异常。
Exception filters can catch exceptions from more than your action methods and page handlers. They’ll run if an exception occurs at these times:
异常筛选器可以捕获来自多个作方法和页面处理程序的异常。如果在以下时间发生异常,它们将运行:
• During model binding or validation
在模型绑定或验证期间
• When the action method or page handler is executing
当作方法或页面处理程序正在执行时
• When an action filter or page filter is executing
当作过滤器或页面过滤器正在执行时
You should note that exception filters won’t catch exceptions thrown in any filters other than action and page filters, so it’s important that your resource and result filters don’t throw exceptions. Similarly, they won’t catch exceptions thrown when executing an IActionResult, such as when rendering a Razor view to HTML.
您应该注意,异常筛选器不会捕获除作和页面筛选器之外的任何筛选器中引发的异常,因此您的资源和结果筛选器不会引发异常非常重要。同样,它们不会捕获在执行 IActionResult 时引发的异常,例如在将 Razor 视图呈现为 HTML 时。
Now that you know why you might want an exception filter, go ahead and implement one for RecipeApiController, as shown next. This lets you safely remove the try-catch block from your action methods, knowing that your filter will catch any errors.
现在,您知道为什么可能需要异常过滤器,请继续为 RecipeApiController 实现一个异常过滤器,如下所示。这样,您就可以安全地从作方法中删除 try-catch 块,因为您知道过滤器将捕获任何错误。
Listing 22.8 The HandleExceptionAttribute exception filter
示例 22.8 HandleExceptionAttribute 异常过滤器
public class HandleExceptionAttribute : ExceptionFilterAttribute #A
{
public override void OnException(ExceptionContext context) #B
{
var error = new ProblemDetails #C
{ #C
Title = "An error occurred", #C
Detail = context.Exception.Message, #C
Status = 500, #C
Type = " https://httpwg.org/specs/rfc9110.html#status.500" #C
}; #C
context.Result = new ObjectResult(error) #D
{ #D
StatusCode = 500 #D
}; #D
context.ExceptionHandled = true; #E
}
}
❶ ExceptionFilterAttribute is an abstract base class that implements IExceptionFilter.
ExceptionFilterAttribute 是实现IExceptionFilter 的抽象基类。
❷ There’s only a single method to override for IExceptionFilter.
只有一个方法可以覆盖 IExceptionFilter。
❸ Building a problem details object to return in the response
构建要在响应中返回的问题详细信息对象
❹ Creates an ObjectResult to serialize the ProblemDetails and to set the response status code
创建一个 ObjectResult 来序列化 ProblemDetails 并设置响应状态代码
❺ Marks the exception as handled to prevent it propagating into the middleware pipeline
将异常标记为已处理,以防止其传播到中间件管道中
It’s quite common to have an exception filter in your application if you are mixing API controllers and Razor Pages in your application, but they’re not always necessary. If you can handle all the exceptions in your application with a single piece of middleware, ditch the exception filters and go with that instead.
如果在应用程序中混合使用 API 控制器和 Razor Pages,则应用程序中的异常筛选器很常见,但它们并不总是必要的。如果可以使用单个中间件处理应用程序中的所有异常,请放弃异常筛选器,改用它。
You’re almost done refactoring your RecipeApiController. You have one more filter type to add: result filters. Custom result filters tend to be relatively rare in the apps I’ve written, but they have their uses, as you’ll see.
您几乎完成了 RecipeApiController 的重构。您还需要添加一种筛选条件类型:结果筛选条件。自定义结果过滤器在我编写的应用程序中往往相对较少,但正如您将看到的,它们有其用途。
22.1.5 Result filters: Customizing action results before they execute
22.1.5 结果过滤器:在执行作结果之前自定义作结果
If everything runs successfully in the pipeline, and there’s no short-circuiting, the next stage of the pipeline after action filters is result filters. These run before and after the IActionResult returned by the action method (or action filters) is executed.
如果管道中的所有内容都成功运行,并且没有短路,则作筛选器后管道的下一阶段是结果筛选器。这些作在执行作方法(或作筛选器)返回的 IActionResult 之前和之后运行。
Warning If the pipeline is short-circuited by setting context.Result, the result filter stage won’t run, but the IActionResult will still be executed to generate the response. The exceptions to this rule are action and page filters, which only short-circuit the action execution, as you saw in chapter 21. Result filters run as normal, as though the action or page handler itself generated the response.
警告:如果通过设置 context 使 pipeline 短路。Result,则结果筛选阶段不会运行,但仍会执行 IActionResult 以生成响应。此规则的例外情况是 action 和 page 过滤器,它们只会使 action 执行短路,如第 21 章所示。结果筛选器照常运行,就像作或页面处理程序本身生成响应一样。
Result filters run immediately after action filters, so many of their use cases are similar, but you typically use result filters to customize the way the IActionResult executes. For example, ASP.NET Core has several result filters built into its framework:
结果筛选条件在作筛选条件之后立即运行,因此它们的许多使用案例相似,但您通常使用结果筛选条件来自定义 IActionResult 的执行方式。例如,ASP.NET Core 的框架中内置了多个结果筛选器:
• ProducesAttribute—This forces a web API result to be serialized to a specific output format. For example, decorating your action method with [Produces ("application/xml")] forces the formatters to try to format the response as XML, even if the client doesn’t list XML in its Accept header.
ProducesAttribute - 强制将 Web API 结果序列化为特定输出格式。例如,使用 [Produces (“application/xml”)] 修饰作方法会强制格式化程序尝试将响应格式化为 XML,即使客户端未在其 Accept 标头中列出 XML。
• FormatFilterAttribute—Decorating an action method with this filter tells the formatter to look for a route value or query string parameter called format and to use that to determine the output format. For example, you could call /api/recipe/11?format=json and FormatFilter will format the response as JSON or call api/recipe/11?format=xml and get the response as XML.
FormatFilterAttribute - 使用此过滤器修饰作方法会告知格式化程序查找名为 format 的路由值或查询字符串参数,并使用它来确定输出格式。例如,您可以调用 /api/recipe/11?format=json,FormatFilter 会将响应格式化为 JSON,或者调用 api/recipe/11?format=xml 并获取 XML 形式的响应。
NOTE Remember that you need to explicitly configure the XML formatters if you want to serialize to XML, as described in chapter 20. For details on formatting results based on the URL, see my blog entry on the topic: http://mng.bz/1rYV.
注意:请记住,如果要序列化为 XML,则需要显式配置 XML 格式化程序,如第 20 章所述。有关基于 URL 设置结果格式的详细信息,请参阅我关于主题 http://mng.bz/1rYV 的博客文章。
As well as controlling the output formatters, you can use result filters to make any last-minute adjustments before IActionResult is executed and the response is generated.
除了控制输出格式化程序外,您还可以使用结果筛选器在执行 IActionResult 并生成响应之前进行任何最后一刻的调整。
As an example of the kind of flexibility available, in the following listing I demonstrate setting the LastModified header, based on the object returned from the action. This is a somewhat contrived example—it’s specific enough to a single action that it likely doesn’t warrant being moved to a result filter—but I hope you get the idea.
作为可用灵活性类型的一个示例,在下面的清单中,我演示了如何根据从作返回的对象设置 LastModified 标头。这是一个有点人为的示例 — 它对单个作足够具体,以至于它不一定需要移动到结果筛选器 — 但我希望您能理解。
Listing 22.9 Setting a response header in a result filter
清单 22.9 在结果过滤器中设置响应头
public class AddLastModifedHeaderAttribute : ResultFilterAttribute #A
{
public override void OnResultExecuting( #B
ResultExecutingContext context) #B
{
if (context.Result is OkObjectResult result #C
&& result.Value is RecipeDetailViewModel detail) #D
{
var viewModelDate = detail.LastModified; #E
context.HttpContext.Response #E
.GetTypedHeaders().LastModified = viewModelDate; #E
}
}
}
❶ ResultFilterAttribute provides a useful base class you can override.
ResultFilterAttribute 提供了一个可以重写的有用基类。
❷ You could also override the Executed method, but the response would already be sent by then.
你也可以重写 Executed 方法,但那时响应已经发送了。
❸ Checks whether the action result returned a 200 Ok result with a view model.
检查作结果是否返回了视图模型的 200 Ok 结果。
❹ Checks whether the view model type is RecipeDetailViewModel . . .
检查视图模型类型是否为 RecipeDetailViewModel . . .
❺ . . . and if it is, fetches the LastModified property and sets the Last-Modified header in the response
. . . .如果是,则获取 LastModified 属性并在响应中设置 Last-Modified 标头
I’ve used another helper base class here, ResultFilterAttribute, so you need to override only a single method to implement the filter. Fetch the current IActionResult, exposed on context.Result, and check that it’s an OkObjectResult instance with a RecipeDetailViewModel value. If it is, fetch the LastModified field from the view model and add a Last-Modified header to the response.
我在这里使用了另一个帮助程序基类 ResultFilterAttribute,因此您只需重写一个方法即可实现筛选器。提取在上下文中公开的当前 IActionResult。Result,并检查它是否是具有 RecipeDetailViewModel 值的 OkObjectResult 实例。如果是,请从视图模型中提取 LastModified 字段,并将 Last-Modified 标头添加到响应中。
Tip GetTypedHeaders() is an extension method that provides strongly typed access to request and response headers. It takes care of parsing and formatting the values for you. You can find it in the Microsoft.AspNetCore.Http namespace.
提示GetTypedHeaders() 是一种扩展方法,它提供对请求和响应标头的强类型访问。它负责为您解析和格式化值。您可以在 Microsoft.AspNetCore.Http 命名空间中找到它。
As with resource and action filters, result filters can implement a method that runs after the result has executed: OnResultExecuted. You can use this method, for example, to inspect exceptions that happened during the execution of IActionResult.
与资源和作筛选器一样,结果筛选器可以实现在结果执行后运行的方法:OnResultExecuted。例如,您可以使用此方法检查在执行 IActionResult 期间发生的异常。
Warning Generally, you can’t modify the response in the OnResultExecuted method, as you may have already started streaming the response to the client.
警告:通常,您无法在 OnResultExecuted 方法中修改响应,因为您可能已经开始将响应流式传输到客户端。
We’ve finished simplifying the RecipeApiController now. By extracting various pieces of functionality to filters, the original controller in listing 22.1 has been simplified to the version in listing 22.2. This is obviously a somewhat extreme and contrived demonstration, and I’m not advocating that filters should always be your go-to option.
我们现在已经完成了 RecipeApiController 的简化。通过将各种功能提取到过滤器中,清单 22.1 中的原始控制器已简化为清单 22.2 中的版本。这显然是一个有点极端和做作的演示,我并不是提倡过滤器应该始终是您的首选。
Tip Filters should be a last resort in most cases. Where possible, it is often preferable to use a simple private method in a controller, or to push functionality into the domain instead of using filters. Filters should generally be used to extract repetitive, HTTP-related, or common cross-cutting code from your controllers.
提示:在大多数情况下,过滤器应该是最后的手段。在可能的情况下,通常最好在控制器中使用简单的私有方法,或者将功能推送到域中而不是使用过滤器。过滤器通常用于从控制器中提取重复的、与 HTTP 相关的或常见的横切代码。
There’s still one more filter we haven’t looked at yet, because it applies only to Razor Pages: page filters.
还有一个筛选器我们还没有查看,因为它仅适用于 Razor Pages:页面筛选器。
22.1.6 Page filters: Customizing model binding for Razor Pages
22.1.6 页面筛选器:自定义 Razor 页面的模型绑定
As already discussed, action filters apply only to controllers and actions; they have no effect on Razor Pages. Similarly, page filters have no effect on controllers and actions. Nevertheless, page filters and action filters fulfill similar roles.
如前所述,作筛选器仅适用于控制器和作;它们对 Razor Pages 没有影响。同样,页面过滤器对控制器和作也没有影响。不过,页面过滤器和作过滤器的作用相似。
As is the case for action filters, the ASP.NET Core framework includes several page filters out of the box. One of these is the Razor Page equivalent of the caching action filter, ResponseCacheFilter, called PageResponseCacheFilter. This works identically to the action-filter equivalent I described in section 22.1.3, setting HTTP caching headers on your Razor Page responses.
与作筛选器一样,ASP.NET Core 框架包含多个现成的页面筛选器。其中一个是缓存作筛选器的 Razor Page 等效项 ResponseCacheFilter,称为 PageResponseCacheFilter。这与我在第 22.1.3 节 在 Razor Page 响应上设置 HTTP 缓存标头中描述的等效作过滤器的工作原理相同。
Page filters are somewhat unusual, as they implement three methods, as discussed in section 22.1.2. In practice, I’ve rarely seen a page filter that implements all three. It’s unusual to need to run code immediately after page handler selection and before model validation. It’s far more common to perform a role directly analogous to action filters. The following listing shows a page filter equivalent to the EnsureRecipeExistsAttribute action filter.
页面过滤器有些不寻常,因为它们实现了三种方法,如 Section 22.1.2 中所述。在实践中,我很少见过实现所有这三个的页面过滤器。在选择页面处理程序之后和模型验证之前需要立即运行代码是不常见的。执行直接类似于作筛选器的角色更为常见。以下清单显示了与 EnsureRecipeExistsAttribute作筛选器等效的页面筛选器。
Listing 22.10 A page filter to check whether a Recipe exists
清单 22.10 用于检查 Recipe 是否存在的页面过滤器
public class PageEnsureRecipeExistsAttribute : Attribute, IPageFilter #A
{
public void OnPageHandlerSelected( #B
PageHandlerSelectedContext context) #B
{} #B
public void OnPageHandlerExecuting( #C
PageHandlerExecutingContext context) #C
{
var service = context.HttpContext.RequestServices #D
.GetService<RecipeService>(); #D
var recipeId = (int) context.HandlerArguments["id"]; #E
if (!service.DoesRecipeExist(recipeId)) #F
{
context.Result = new NotFoundResult(); #G
}
}
public void OnPageHandlerExecuted( #H
PageHandlerExecutedContext context) #H
{ } #H
}
❶ Implements IPageFilter and as an attribute so you can decorate the Razor Page PageModel
实现 IPageFilter 并作为属性,以便您可以装饰 Razor Page PageModel
❷ Executed after handler selection and before model binding—not used in this example
在处理程序选择之后和模型绑定之前执行 - 本例中未使用
❸ Executed after model binding and validation, and before page handler execution
在模型绑定和验证之后以及页面处理程序执行之前执行
❹ Fetches an instance of RecipeService from the DI container
从 DI 容器中获取 RecipeService 的实例
❺ Retrieves the id parameter that will be passed to the page handler method when it executes
检索 id 参数,该参数将在执行时传递给页面处理程序方法
❻ Checks whether a Recipe entity with the given RecipeId exists . . .
检查是否存在具有给定 RecipeId 的 Recipe 实体 . . .
❼ . . . and if it doesn’t exist, returns a 404 Not Found result and short-circuits the pipeline
. . . .如果不存在,则返回 404 Not Found 结果,并在页面处理程序执行(或短路)后将管道
❽ Executed after page handler execution (or short-circuiting)—not used in this example
Executed 短路 — 本例中未使用
The page filter is similar to the action filter equivalent. The most obvious difference is the need to implement three methods to satisfy the IPageFilter interface. You’ll commonly want to implement the OnPageHandlerExecuting method, which runs after model binding and validation, and before the page handler executes.
页面过滤器类似于等效的动作过滤器。最明显的区别是需要实现三种方法来满足 IPageFilter 接口。您通常需要实现 OnPageHandlerExecuting 方法,该方法在模型绑定和验证之后、页面处理程序执行之前运行。
A subtle difference between the action filter code and the page filter code is that the action filter accesses the model-bound action arguments using context.ActionArguments. The page filter uses context.HandlerArguments in the example, but there’s also another option.
作筛选条件代码和页面筛选条件代码之间的细微差别是,作筛选条件使用上下文访问模型绑定的作参数。ActionArguments 的 API 参数。页面过滤器使用 context。HandlerArguments 的 HandlerArguments 进行匹配,但还有另一个选项。
Remember from chapter 16 that Razor Pages often bind to public properties on the PageModel using the [BindProperty] attribute. You can access those properties directly instead of using magic strings by casting a HandlerInstance property to the correct PageModel type and accessing the property directly, as in this example:
请记住,在第 16 章中,Razor Pages 通常使用 [BindProperty] 属性绑定到 PageModel 上的公共属性。你可以通过将 HandlerInstance 属性强制转换为正确的 PageModel 类型并直接访问该属性,直接访问这些属性,而不是使用魔术字符串,如下例所示:
var recipeId = ((ViewRecipePageModel)context.HandlerInstance).Id
This is similar to the way the ControllerBase class implements IActionFilter and PageModel implements IPageFilter and IAsyncPageFilterT. If you want to create an action filter for a single Razor Page, you could save yourself the trouble of creating a separate page filter and override these methods directly in your Razor Page.
这类似于 ControllerBase 类实现 IActionFilter 和 PageModel 实现 IPageFilter 和 IAsyncPageFilterT 的方式。如果要为单个 Razor 页面创建作筛选器,则可以省去创建单独页面筛选器的麻烦,并直接在 Razor 页面中重写这些方法。
Tip I generally find it’s not worth the hassle of using page filters unless you have a common requirement. The extra level of indirection that page filters add, coupled with the typically bespoke nature of individual Razor Pages, means that I normally find they aren’t worth using. Your mileage may vary, of course, but don’t jump to them as a first option.
提示:我通常发现不值得使用页面过滤器的麻烦,除非你有共同的要求。页面过滤器添加的额外间接级别,再加上单个 Razor 页面的典型定制性质,意味着我通常会发现它们不值得使用。当然,您的里程可能会有所不同,但不要将它们作为首选。
That brings us to the end of this detailed look at each of the filters in the MVC pipeline. Looking back and comparing listings 22.1 and 22.2, you can see filters allowed us to refactor the controllers and make the intent of each action method much clearer. Writing your code in this way makes it easier to reason about, as each filter and action has a single responsibility.
这样,我们就结束了对 MVC 管道中每个过滤器的详细介绍。回顾并比较清单 22.1 和 22.2,您可以看到过滤器允许我们重构控制器,并使每个作方法的意图更加清晰。以这种方式编写代码可以更轻松地进行推理,因为每个过滤器和作都有单一的责任。
In the next section we’ll take a slight detour into exactly what happens when you short-circuit a filter. I’ve described how to do this, by setting the context.Result property on a filter, but I haven’t described exactly what happens. For example, what if there are multiple filters in the stage when it’s short-circuited? Do those still run?
在下一节中,我们将稍微绕道介绍一下当 filter 短路时会发生什么。我已经介绍了如何通过设置上下文来执行此作。Result 属性,但我还没有具体描述会发生什么。例如,如果 stage 中有多个 filter 短路怎么办?那些还在运行吗?
22.2 Understanding pipeline short-circuiting
22.2 了解管道短路
In this short section you’ll learn about the details of filter-pipeline short-circuiting. You’ll see what happens to the other filters in a stage when the pipeline is short-circuited and how to short-circuit each type of filter.
在这个简短的部分中,您将了解 filter-pipeline 短路的详细信息。您将看到当管道短路时,某个阶段中的其他过滤器会发生什么情况,以及如何使每种类型的过滤器短路。
A brief warning: the topic of filter short-circuiting can be a little confusing. Unlike middleware short-circuiting, which is cut-and-dried, the filter pipeline is a bit more nuanced. Luckily, you won’t often need to dig into it, but when you do, you’ll be glad for the detail.
一个简短的警告:滤波器短路的话题可能有点令人困惑。与中间件短路不同,筛选器管道更加微妙。幸运的是,您通常不需要深入研究它,但当您这样做时,您会为细节感到高兴。
You short-circuit the authorization, resource, action, page, and result filters by setting context.Result to IActionResult. Setting an action result in this way causes some or all of the remaining pipeline to be bypassed. But the filter pipeline isn’t entirely linear, as you saw in chapter 21, so short-circuiting doesn’t always do an about-face back down the pipeline. For example, short-circuited action filters bypass only action method execution; the result filters and result execution stages still run.
您可以通过设置 context 来短路 authorization、resource、action、page 和 result 过滤器。Result 设置为 IActionResult。以这种方式设置作结果会导致绕过部分或全部剩余管道。但是 filter pipeline 并不是完全线性的,正如你在第 21 章中看到的那样,所以短路并不总是在管道上做一个反转。例如,短路的动作过滤器仅绕过动作方法的执行;结果筛选条件和结果执行阶段仍在运行。
The other difficultly is what happens if you have more than one filter in a stage. Let’s say you have three resource filters executing in a pipeline. What happens if the second filter causes a short circuit? Any remaining filters are bypassed, but the first resource filter has already run its Executing command, as shown in figure 22.2. This earlier filter gets to run its Executed command too, with context.Cancelled = true, indicating that a filter in that stage (the resource filter stage) short-circuited the pipeline.
另一个困难是在一个阶段中有多个过滤器时会发生什么。假设您在一个管道中执行了三个资源筛选器。如果第二个滤波器导致短路怎么办?任何剩余的过滤器都将被绕过,但第一个资源过滤器已经运行了其 Executing 命令,如图 22.2 所示。这个前面的过滤器也可以运行它的 Executed 命令,其中包含 context。Cancelled = true,指示该阶段(资源筛选器阶段)中的筛选器使管道短路。
Figure 22.2 The effect of short-circuiting a resource filter on other resource filters in that stage. Later filters in the stage won’t run at all, but earlier filters run their OnResourceExecuted function.
图 22.2 在该阶段中,将资源过滤器短路对其他资源过滤器的影响。阶段中较晚的筛选器根本不会运行,但较早的筛选器会运行其 OnResourceExecuted 函数。
Running result filters after short-circuits with IAlwaysRunResultFilter
使用 IAlwaysRunResultFilterResult filters are designed to wrap the execution of an IActionResult returned by an action method or action filter so that you can customize how the action result is executed. However, this customization doesn’t apply to the IActionResult set when you short-circuit the filter pipeline by setting context.Result in an authorization filter, resource filter, or exception filter.
在短路后运行结果筛选器 结果筛选器旨在包装作方法或作筛选器返回的 IActionResult 的执行,以便您可以自定义作结果的执行方式。但是,当您通过设置 context 使筛选器管道短路时,此自定义不适用于 IActionResult 集。生成授权筛选条件、资源筛选条件或异常筛选条件。That’s often not a problem, as many result filters are designed to handle “happy path” transformations. But sometimes you want to make sure that a transformation is always applied to an IActionResult, regardless of whether it was returned by an action method or a short-circuiting filter.
这通常不是问题,因为许多结果筛选器都旨在处理 “happy path” 转换。但有时你希望确保转换始终应用于 IActionResult,而不管它是由作方法还是短路筛选器返回的。For those cases, you can implement IAlwaysRunResultFilter or IAsyncAlwaysRunResultFilter. These interfaces extend (and are identical) to the standard result filter interfaces, so they run like normal result filters in the filter pipeline. But these interfaces mark the filter to also run after an authorization filter, resource filter, or exception filter short-circuits the pipeline, where standard result filters won’t run.
对于这些情况,您可以实现 IAlwaysRunResultFilter 或 IAsyncAlwaysRunResultFilter。这些接口扩展(并且相同)到标准结果筛选器接口,因此它们像筛选器管道中的普通结果筛选器一样运行。但是,这些接口将筛选器标记为在授权筛选器、资源筛选器或异常筛选器使管道短路后也运行,其中标准结果筛选器不会运行。You can use IAlwaysRunResultFilter to ensure that certain action results are always updated. For example, the documentation shows how to use an IAlwaysRunResultFilter to convert a 415 StatusCodeResult to a 422 StatusCodeResult, regardless of the source of the action result. See the “IAlwaysRunResultFilter and IAsyncAlwaysRunResultFilter” section of Microsoft’s “Filters in ASP.NET Core” documentation: http://mng.bz/JDo0.
您可以使用 IAlwaysRunResultFilter 来确保某些作结果始终更新。例如,该文档显示了如何使用 IAlwaysRunResultFilter 将 415 StatusCodeResult 转换为 422 StatusCodeResult,而不管作结果的来源如何。请参阅 Microsoft 的“ASP.NET Core 中的筛选器”文档的“IAlwaysRunResultFilter 和 IAsyncAlwaysRunResultFilter”部分:http://mng.bz/JDo0。
Understanding which other filters run when you short-circuit a filter can be somewhat of a chore, but I’ve summarized each filter in table 22.1. You’ll also find it useful to refer to the pipeline diagrams in chapter 21 to visualize the shape of the pipeline when thinking about short circuits.
了解在使 filter 短路时运行哪些其他 filters 可能有点麻烦,但我在表 22.1 中总结了每个 filter 。在考虑短路时,您还会发现参考第 21 章中的流水线图以可视化流水线的形状很有用。
Table 22.1 The effect of short-circuiting filters on filter-pipeline execution
表 22.1 短路 filters 对 filter-pipeline 执行的影响
Filter type | How to short-circuit? | What else runs? |
---|---|---|
Authorization filters | Set context.Result. | Runs only IAlwaysRunResultFilters. |
Resource filters | Set context.Result. | Resource-filter *Executed functions from earlier filters run with context.Cancelled = true. Runs IAlwaysRunResultFilters before executing the IActionResult. |
Action filters | Set context.Result. | Bypasses only action method execution. Action filters earlier in the pipeline run their Executed methods with context.Cancelled = true, then result filters, result execution, and resource filters’ Executed methods all run as normal. |
Page filters | Set context.Result in OnPageHandlerSelected. | Bypasses only page handler execution. Page filters earlier in the pipeline run their Executed methods with context.Cancelled = true, then result filters, result execution, and resource filters’ Executed methods all run as normal. |
Exception filters | Set context.Result and Exception.Handled = true. | All resource-filter *Executed functions run. Runs IAlwaysRunResultFilters before executing the IActionResult. |
Result filters | Set context.Cancelled = true. | Result filters earlier in the pipeline run their Executed functions with context.Cancelled = true. All resource-filter Executed functions run as normal. |
The most interesting point here is that short-circuiting an action filter (or a page filter) doesn’t short-circuit much of the pipeline at all. In fact, it bypasses only later action filters and the action method execution itself. By building primarily action filters, you can ensure that other filters, such as result filters that define the output format, run as usual, even when your action filters short-circuit.
这里最有趣的一点是,短路作筛选器(或页面筛选器)根本不会使管道的大部分短路。事实上,它只绕过后面的作筛选器和作方法执行本身。通过主要构建作筛选器,您可以确保其他筛选器(例如定义输出格式的结果筛选器)照常运行,即使作筛选器短路时也是如此。
The last thing I’d like to talk about in this chapter is how to use DI with your filters. You saw in chapters 8 and 9 that DI is integral to ASP.NET Core, and in the next section you’ll see how to design your filters so that the framework can inject service dependencies into them for you.
本章我想谈的最后一件事是如何将 DI 与你的过滤器一起使用。您在第 8 章和第 9 章中看到了 DI 是 ASP.NET Core 不可或缺的一部分,在下一节中,您将了解如何设计过滤器,以便框架可以为您注入服务依赖项。
22.3 Using dependency injection with filter attributes
22.3 将依赖项注入与 filter 属性一起使用
In this section you’ll learn how to inject services into your filters so you can take advantage of the simplicity of DI in your filters. You’ll learn to use two helper filters to achieve this, TypeFilterAttribute and ServiceFilterAttribute, and you’ll see how they can be used to simplify the action filter you defined in section 22.1.3.
在本节中,您将学习如何将服务注入过滤器,以便您可以在过滤器中利用 DI 的简单性。您将学习使用两个辅助过滤器来实现此目的,TypeFilterAttribute和ServiceFilterAttribute,并且您将了解如何使用它们来简化您在第 22.1.3 节中定义的作过滤器。
The filters we’ve created so far have been created as attributes. This is useful for applying filters to action methods and controllers, but it means you can’t use DI to inject services into the constructor. C# attributes don’t let you pass dependencies into their constructors (other than constant values), and they’re created as singletons, so there’s only a single instance of an attribute for the lifetime of your app. So what happens if you need to access a transient or scoped service from inside the singleton attribute?
到目前为止,我们创建的过滤器已创建为 attributes。这对于将筛选器应用于作方法和控制器非常有用,但这意味着您不能使用 DI 将服务注入构造函数。C# 属性不允许将依赖项传递到其构造函数中(常量值除外),并且它们被创建为单一实例,因此在应用的生命周期内只有一个属性实例。那么,如果您需要从 singleton 属性内部访问临时或范围服务,会发生什么情况呢?
Listing 22.6 showed one way of doing this, using a pseudo-service locator pattern to reach into the DI container and pluck out RecipeService at runtime. This works but is generally frowned upon as a pattern in favor of proper DI. So how can you add DI to your filters?
清单 22.6 展示了一种实现此目的的方法,使用伪服务定位器模式进入 DI 容器并在运行时提取 RecipeService。这有效,但通常不被看作是一种支持适当 DI 的模式。那么如何将 DI 添加到过滤器中呢?
The key is to split the filter in two. Instead of creating a class that’s both an attribute and a filter, create a filter class that contains the functionality and an attribute that tells the framework when and where to use the filter.
关键是将过滤器一分为二。不要创建一个既是属性又是筛选器的类,而应创建一个包含功能和属性的筛选器类,该类告诉框架何时何地使用筛选器。
Let’s apply this to the action filter from listing 22.6. Previously, I derived from ActionFilterAttribute and obtained an instance of RecipeService from the context passed to the method. In the following listing I show two classes, EnsureRecipeExistsFilter and EnsureRecipeExistsAttribute. The filter class is responsible for the functionality and takes in RecipeService as a constructor dependency.
让我们将其应用于清单 22.6 中的 action filter。以前,我从 ActionFilterAttribute 派生,并从传递给该方法的上下文中获取 RecipeService 的实例。在下面的清单中,我显示了两个类,EnsureRecipeExistsFilter 和 EnsureRecipeExistsAttribute。filter 类负责功能,并将 RecipeService 作为构造函数依赖项。
Listing 22.11 Using DI in a filter by not deriving from Attribute
清单 22.11 在过滤器中使用 DI 而不是从 Attribute 派生
public class EnsureRecipeExistsFilter : IActionFilter #A
{
private readonly RecipeService _service; #B
public EnsureRecipeExistsFilter(RecipeService service) #B
{ #B
_service = service; #B
} #B
public void OnActionExecuting(ActionExecutingContext context) #C
{ #C
var recipeId = (int) context.ActionArguments["id"]; #C
if (!_service.DoesRecipeExist(recipeId)) #C
{ #C
context.Result = new NotFoundResult(); #C
} #C
} #C
public void OnActionExecuted(ActionExecutedContext context) { } #D
}
public class EnsureRecipeExistsAttribute : TypeFilterAttribute #E
{
public EnsureRecipeExistsAttribute() #F
: base(typeof(EnsureRecipeExistsFilter)) {} #F
}
❶ Doesn’t derive from an Attribute class
不从 Attribute 类派生
❷ RecipeService is injected into the constructor.
RecipeService 被注入到构造函数中。
❸ The rest of the method remains the same.
方法的其余部分保持不变。
❹ You must implement the Executed action to satisfy the interface.
您必须实现 Executed作才能满足接口。
❺ Derives from TypeFilter, which is used to fill dependencies using the DI container
派生自 TypeFilter,用于使用 DI 容器填充依赖项
❻ Passes the type EnsureRecipeExistsFilter as an argument to the base TypeFilter constructor
将类型 EnsureRecipeExistsFilter 作为参数传递给基本 TypeFilter 构造函数
EnsureRecipeExistsFilter is a valid filter; you could use it on its own by adding it as a global filter (as global filters don’t need to be attributes). But you can’t use it directly by decorating controller classes and action methods, as it’s not an attribute. That’s where EnsureRecipeExistsAttribute comes in.
EnsureRecipeExistsFilter 是有效的筛选器;您可以通过将其添加为全局过滤器来单独使用它(因为全局过滤器不需要是属性)。但是你不能通过装饰控制器类和作方法来直接使用它,因为它不是一个属性。这就是 EnsureRecipeExistsAttribute 的用武之地。
You can decorate your methods with EnsureRecipeExistsAttribute instead. This attribute inherits from TypeFilterAttribute and passes the Type of filter to create as an argument to the base constructor. This attribute acts as a factory for EnsureRecipeExistsFilter by implementing IFilterFactory.
您可以改用 EnsureRecipeExistsAttribute 来修饰您的方法。此属性继承自 TypeFilterAttribute,并将要创建的过滤器的 Type 作为参数传递给基本构造函数。此属性通过实现 IFilterFactory 充当 EnsureRecipeExistsFilter 的工厂。
When ASP.NET Core initially loads your app, it scans your actions and controllers, looking for filters and filter factories. It uses these to form a filter pipeline for every action in your app, as shown in figure 22.3.
当 ASP.NET Core 最初加载您的应用程序时,它会扫描您的作和控制器,查找过滤器和过滤器工厂。它使用这些数据为应用程序中的每个作形成一个 filter pipeline ,如图 22.3 所示。
Figure 22.3 The framework scans your app on startup to find both filters and attributes that implement IFilterFactory. At runtime, the framework calls CreateInstance() to get an instance of the filter
图 22.3 框架在启动时扫描您的应用程序,以查找实现 IFilterFactory 的过滤器和属性。在运行时,框架调用 CreateInstance() 来获取过滤器的实例
When an action decorated with EnsureRecipeExistsAttribute is called, the framework calls CreateInstance() on the IFilterFactory attribute. This creates a new instance of EnsureRecipeExistsFilter and uses the DI container to populate its dependencies (RecipeService).
调用使用 EnsureRecipeExistsAttribute 修饰的作时,框架将对 IFilterFactory 属性调用 CreateInstance()。这将创建一个新的 EnsureRecipeExistsFilter 实例,并使用 DI 容器填充其依赖项 (RecipeService)。
By using this IFilterFactory approach, you get the best of both worlds: you can decorate your controllers and actions with attributes, and you can use DI in your filters. Out of the box, two similar classes provide this functionality, which have slightly different behaviors:
通过使用这种 IFilterFactory 方法,您可以两全其美:您可以使用属性装饰控制器和作,并且可以在过滤器中使用 DI。开箱即用,两个类似的类提供了此功能,它们的行为略有不同:
• TypeFilterAttribute—Loads all the filter’s dependencies from the DI container and uses them to create a new instance of the filter.
TypeFilterAttribute - 从 DI 容器中加载所有筛选器的依赖项,并使用它们创建筛选器的新实例。
• ServiceFilterAttribute—Loads the filter itself from the DI container. The DI container takes care of the service lifetime and building the dependency graph. Unfortunately, you must also explicitly register your filter with the DI container:
ServiceFilterAttribute - 从 DI 容器加载筛选器本身。DI 容器负责服务生命周期并构建依赖项关系图。遗憾的是,您还必须向 DI 容器显式注册过滤器:
builder.Services.AddTransient<EnsureRecipeExistsFilter>();
Tip You can register your services with any lifetime you choose. If your service is registered as a singleton, you can consider setting the IsReusable flag, as described in the documentation: http://mng.bz/d1JD.
提示您可以使用您选择的任何生命周期来注册您的服务。如果您的服务注册为单一实例,则可以考虑设置 IsReusable 标志,如文档中所述:http://mng.bz/d1JD。
If you choose to use ServiceFilterAttribute instead of TypeFilterAttribute, and register the EnsureRecipeExistsFilter as a service in the DI container, you can apply the ServiceFilterAttribute directly to an action method:
如果您选择使用 ServiceFilterAttribute 而不是 TypeFilterAttribute,并在 DI 容器中将 EnsureRecipeExistsFilter 注册为服务,则可以将 ServiceFilterAttribute 直接应用于作方法:
[ServiceFilter(typeof(EnsureRecipeExistsFilter))]
public IActionResult Index() => Ok();
Whether you choose to use TypeFilterAttribute or ServiceFilterAttribute is somewhat a matter of preference, and you can always implement a custom IFilterFactory if you need to. The key takeaway is that you can now use DI in your filters. If you don’t need to use DI for a filter, implement it as an attribute directly, for simplicity.
选择使用 TypeFilterAttribute 还是 ServiceFilterAttribute 在某种程度上是一个首选项问题,如果需要,您始终可以实现自定义 IFilterFactory。关键要点是您现在可以在过滤器中使用 DI。如果您不需要将 DI 用于过滤器,请直接将其作为属性实现,以便简单起见。
Tip I like to create my filters as a nested class of the attribute class when using this pattern. This keeps all the code nicely contained in a single file and indicates the relationship between the classes.
提示:使用此模式时,我喜欢将过滤器创建为 attribute 类的嵌套类。这将使所有代码很好地包含在单个文件中,并指示类之间的关系。
That brings us to the end of this chapter on the filter pipeline. Filters are a somewhat advanced topic, in that they aren’t strictly necessary for building basic apps, but I find them extremely useful for ensuring that my controller and action methods are simple and easy to understand.
这将我们带到了本章关于过滤器管道的结尾。筛选器是一个比较高级的主题,因为它们对于构建基本应用程序并不是绝对必要的,但我发现它们对于确保我的控制器和作方法简单易懂非常有用。
In the next chapter we’ll take our first look at securing your app. We’ll discuss the difference between authentication and authorization, the concept of identity in ASP.NET Core, and how you can use the ASP.NET Core Identity system to let users register and log in to your app.
在下一章中,我们将首先了解如何保护您的应用程序。我们将讨论身份验证和授权之间的区别、ASP.NET Core 中的身份概念,以及如何使用 ASP.NET Core Identity 系统让用户注册和登录您的应用。
22.4 Summary
22.4 总结
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.
筛选器管道作为 MVC 或 Razor Pages 执行的一部分执行。它由授权筛选条件、资源筛选条件、作筛选条件、页面筛选条件、异常筛选条件和结果筛选条件组成。
ASP.NET Core includes many built-in filters, but you can also create custom filters tailored to your application. You can use custom filters to extract common cross-cutting functionality out of your MVC controllers and Razor Pages, reducing duplication and ensuring consistency across your endpoints.
ASP.NET Core 包含许多内置筛选器,但您也可以创建针对您的应用程序定制的自定义筛选器。您可以使用自定义筛选器从 MVC 控制器和 Razor 页面中提取常见的横切功能,从而减少重复并确保端点之间的一致性。
Authorization filters run first in the pipeline and control access to APIs. ASP.NET Core includes an [Authorization] attribute that you can apply to action methods so that only logged-in users can execute the action.
授权过滤器首先在管道中运行并控制对 API 的访问。ASP.NET Core 包含一个 [Authorization] 属性,您可以将其应用于作方法,以便只有登录用户才能执行该作。
Resource filters run after authorization filters and again after an IActionResult has been executed. They can be used to short-circuit the pipeline so that an action method is never executed. They can also be used to customize the model-binding process for an action method.
资源筛选条件在授权筛选条件之后运行,并在执行 IActionResult 后再次运行。它们可用于使管道短路,以便永远不会执行作方法。它们还可用于自定义作方法的模型绑定过程。
Action filters run after model binding has occurred and before an action method executes. They also run after the action method has executed. They can be used to extract common code out of an action method to prevent duplication. They don’t execute for Razor Pages, only for MVC controllers.
作筛选器在模型绑定发生之后和作方法执行之前运行。它们还会在执行作方法后运行。它们可用于从 action method 中提取公共代码,以防止重复。它们不为 Razor Pages 执行,只为 MVC 控制器执行。
The ControllerBase base class also implements IActionFilter and IAsyncActionFilter. They run at the start and end of the action filter pipeline, regardless of the ordering or scope of other action filters. They can be used to create action filters that are specific to one controller.
ControllerBase 基类还实现 IActionFilter 和 IAsyncActionFilter。它们在作筛选器管道的开头和结尾运行,而不管其他作筛选器的顺序或范围如何。它们可用于创建特定于一个控制器的作筛选器。
Page filters run three times: after page handler selection, after model binding, and after the page handler method executes. You can use page filters for similar purposes as action filters. Page filters execute only for Razor Pages; they don’t run for MVC controllers.
页面过滤器运行三次:选择页面处理程序后、模型绑定后和页面处理程序方法执行后。您可以将页面过滤器用于与作过滤器类似的目的。页面筛选器仅对 Razor Pages 执行;它们不为 MVC 控制器运行。
Razor Page PageModels implement IPageFilter and IAsyncPageFilter, so they can be used to implement page-specific page filters. These are rarely used, as you can typically achieve similar results with simple private methods.
Razor Page PageModel 实现 IPageFilter 和 IAsyncPageFilter,因此它们可用于实现特定于页面的页面筛选器。这些方法很少使用,因为通常可以使用简单的私有方法获得类似的结果。
Exception filters execute after action and page filters, when an action method or page handler has thrown an exception. They can be used to provide custom error handling specific to the action executed.
当作方法或页面处理程序引发异常时,异常筛选器在作和页面筛选器之后执行。它们可用于提供特定于所执行作的自定义错误处理。
Generally, you should handle exceptions at the middleware level, but you can use exception filters to customize how you handle exceptions for specific actions, controllers, or Razor Pages.
通常,您应该在中间件级别处理异常,但您可以使用异常筛选器来自定义处理特定作、控制器或 Razor Pages 的异常的方式。
Result filters run before and after an IActionResult is executed. You can use them to control how the action result is executed or to completely change the action result that will be executed.
结果筛选器在执行 IActionResult 之前和之后运行。您可以使用它们来控制作结果的执行方式,或完全更改将要执行的作结果。
All filters can short-circuit the pipeline by setting a response. This generally prevents the request progressing further in the filter pipeline, but the exact behavior varies with the type of filter that is short-circuited.
所有过滤器都可以通过设置响应来短路管道。这通常可以防止请求在筛选条件管道中进一步进行,但具体行为会因短路的筛选条件类型而异。
Result filters aren’t executed when you short-circuit the pipeline using authorization, resource, or exception filters. You can ensure that result filters also run for these short-circuit cases by implementing a result filter as IAlwaysRunResultFilter or IAsyncAlwaysRunResultFilter.
当您使用授权、资源或异常筛选条件对管道进行短路时,不会执行结果筛选条件。您可以通过将结果筛选器实现为 IAlwaysRunResultFilter 或 IAsyncAlwaysRunResultFilter 来确保结果筛选器也针对这些短路情况运行。
You can use ServiceFilterAttribute and TypeFilterAttribute to allow dependency injection in your custom filters. ServiceFilterAttribute requires that you register your filter and all its dependencies with the DI container, whereas TypeFilterAttribute requires only that the filter’s dependencies have been registered.
您可以使用 ServiceFilterAttribute 和 TypeFilterAttribute 在自定义筛选条件中允许依赖项注入。ServiceFilterAttribute 要求您向 DI 容器注册过滤器及其所有依赖项,而 TypeFilterAttribute 仅要求已注册过滤器的依赖项。