ASP.NET Core in Action 4 Handling requests with the middleware pipeline

4 Handling requests with the middleware pipeline

使用中间件管道处理请求

This chapter covers

本章涵盖

  • Understanding middleware
    了解中间件
  • Serving static files using middleware
    使用中间件提供静态文件
  • Adding functionality using middleware
    使用中间件添加功能
  • Combining middleware to form a pipeline
    组合中间件形成管道
  • Handling exceptions and errors with middleware
    使用中间件处理异常和错误

In chapter 3 you had a whistle-stop tour of a complete ASP.NET Core application to see how the components come together to create a web application. In this chapter, we’ll focus on one small subsection: the middleware pipeline.

在第 3 章中,您简要介绍了一个完整的 ASP.NET Core 应用程序,以了解这些组件如何组合在一起以创建 Web 应用程序。在本章中,我们将重点介绍一个小部分:中间件管道。

In ASP.NET Core, middleware consists of C# classes or functions that handle an HTTP request or response. Middleware is chained together, with the output of one acting as the input to the next to form a pipeline.

在 ASP.NET Core 中,中间件由处理 HTTP 请求或响应的 C# 类或函数组成。中间件链接在一起,一个中间件的输出充当下一个中间件的输入,形成一个管道。

The middleware pipeline is one of the most important parts of configuration for defining how your application behaves and how it responds to requests. Understanding how to build and compose middleware is key to adding functionality to your applications.

中间件管道是配置中最重要的部分之一,用于定义应用程序的行为方式和响应请求的方式。了解如何构建和组合中间件是向应用程序添加功能的关键。

In this chapter you’ll learn what middleware is and how to use it to create a pipeline. You’ll see how you can chain multiple middleware components together, with each component adding a discrete piece of functionality. The examples in this chapter are limited to using existing middleware components, showing how to arrange them in the correct way for your application. In chapter 31 you’ll learn how to build your own middleware components and incorporate them into the pipeline.

在本章中,您将了解什么是中间件以及如何使用它来创建管道。您将了解如何将多个中间件组件链接在一起,每个组件添加一个独立的功能。本章中的示例仅限于使用现有的中间件组件,展示了如何为您的应用程序以正确的方式排列它们。在第 31 章中,您将学习如何构建自己的中间件组件并将其合并到管道中。

We’ll begin by looking at the concept of middleware, all the things you can achieve with it, and how a middleware component often maps to a cross-cutting concern. These functions of an application cut across multiple different layers. Logging, error handling, and security are classic cross-cutting concerns that are required by many parts of your application. Because all requests pass through the middleware pipeline, it’s the preferred location to configure and handle this functionality.

我们首先要了解中间件的概念,你可以用它实现的所有事情,以及中间件组件通常如何映射到一个横切关注点。应用程序的这些功能跨越多个不同的层。日志记录、错误处理和安全性是应用程序的许多部分都需要的典型横切关注点。由于所有请求都通过中间件管道传递,因此它是配置和处理此功能的首选位置。

In section 4.2 I’ll explain how you can compose individual middleware components into a pipeline. You’ll start out small, with a web app that displays only a holding page. From there, you’ll learn how to build a simple static-file server that returns requested files from a folder on disk.

在 Section 4.2 中,我将解释如何将单个中间件组件组合到一个管道中。您将从小规模开始,使用仅显示保留页的 Web 应用程序。从那里,您将学习如何构建一个简单的静态文件服务器,该服务器从磁盘上的文件夹返回请求的文件。

Next, you’ll move on to a more complex pipeline containing multiple middleware. In this example you’ll explore the importance of ordering in the middleware pipeline, and you’ll see how requests are handled when your pipeline contains multiple middleware.

接下来,您将转到包含多个中间件的更复杂的管道。在此示例中,您将探索中间件管道中排序的重要性,并了解当管道包含多个中间件时如何处理请求。

In section 4.3 you’ll learn how you can use middleware to deal with an important aspect of any application: error handling. Errors are a fact of life for all applications, so it’s important that you account for them when building your app.

在 Section 4.3 中,您将学习如何使用 middleware 来处理任何应用程序的一个重要方面:错误处理。错误是所有应用程序都不可避免的事实,因此在构建应用程序时考虑错误非常重要。

You can handle errors in a few ways. Errors are among the classic cross-cutting concerns, and middleware is well placed to provide the required functionality. In section 4.3 I’ll show how you can handle exceptions with middleware provided by Microsoft. In particular, you’ll learn about two different components:

您可以通过多种方式处理错误。错误是典型的横切关注点之一,中间件可以很好地提供所需的功能。在 Section 4.3 中,我将展示如何使用 Microsoft 提供的中间件处理异常。具体而言,您将了解两个不同的组件:

  • DeveloperExceptionPageMiddleware—Provides quick error feedback when building an application
    DeveloperExceptionPageMiddleware— 在构建应用程序时提供快速错误反馈

  • ExceptionHandlerMiddleware—Provides a generic error page in production so that you don’t leak any sensitive details
    ExceptionHandlerMiddleware- 在生产中提供通用错误页面,以便您不会泄露任何敏感详细信息

You won’t see how to build your own middleware in this chapter; instead, you’ll see that you can go a long way by using the components provided as part of ASP.NET Core. When you understand the middleware pipeline and its behavior, you’ll find it much easier to understand when and why custom middleware is required. With that in mind, let’s dive in!

在本章中,您不会看到如何构建自己的中间件;相反,您会发现,通过使用作为 ASP.NET Core 的一部分提供的组件,您可以走很长一段路。当您了解中间件管道及其行为时,您会发现更容易理解何时以及为什么需要自定义中间件。考虑到这一点,让我们开始吧!

4.1 Defining middleware

4.1 定义中间件

The word middleware is used in a variety of contexts in software development and IT, but it’s not a particularly descriptive word.

中间件这个词在软件开发和 IT 中的各种上下文中都有使用,但它不是一个特别具有描述性的词。

In ASP.NET Core, middleware is C# classes[1] that can handle an HTTP request or response. Middleware can
在 ASP.NET Core 中,中间件是可以处理 HTTP 请求或响应的 C# 类[1]。中间件可以

  • Handle an incoming HTTP request by generating an HTTP response
    通过生成 HTTP 响应来处理传入的 HTTP 请求

  • Process an incoming HTTP request, modify it, and pass it on to another piece of middleware
    处理传入的 HTTP 请求,对其进行修改,并将其传递给另一个中间件

  • Process an outgoing HTTP response, modify it, and pass it on to another piece of middleware or to the ASP.NET Core web server
    处理传出的 HTTP 响应,对其进行修改,并将其传递给另一个中间件或 ASP.NET Core Web 服务器

You can use middleware in a multitude of ways in your own applications. A piece of logging middleware, for example, might note when a request arrived and then pass it on to another piece of middleware. Meanwhile, a static-file middleware component might spot an incoming request for an image with a specific name, load the image from disk, and send it back to the user without passing it on.

您可以在自己的应用程序中以多种方式使用中间件。例如,一个日志记录中间件可能会记录请求何时到达,然后将其传递给另一个中间件。同时,静态文件中间件组件可能会发现对具有特定名称的图像的传入请求,从磁盘加载图像,并将其发送回给用户,而无需传递。

The most important piece of middleware in most ASP.NET Core applications is the EndpointMiddleware class. This class normally generates all your HTML and JavaScript Object Notation (JSON) responses, and is the focus of most of this book. Like image-resizing middleware, it typically receives a request, generates a response, and then sends it back to the user (figure 4.1).

在大多数 ASP.NET Core 应用程序中,最重要的中间件是类。此类通常会生成所有 HTML 和 JavaScript 对象表示法 (JSON) 响应,并且是本书大部分内容的重点。与图像大小调整中间件一样,它通常接收请求,生成响应,然后将其发送回给用户(图 4.1)。

alt text

Figure 4.1 Example of a middleware pipeline. Each middleware component handles the request and passes it on to the next middleware component in the pipeline. After a middleware component generates a response, it passes the response back through the pipeline. When it reaches the ASP.NET Core web server, the response is sent to the user’s browser.
图 4.1 中间件管道示例。每个中间件组件都处理请求并将其传递给管道中的下一个中间件组件。中间件组件生成响应后,它会通过管道将响应传回。当它到达 ASP.NET Core Web 服务器时,响应将发送到用户的浏览器。

Definition This arrangement—whereby a piece of middleware can call another piece of middleware, which in turn can call another, and so on—is referred to as a pipeline. You can think of each piece of middleware as being like a section of pipe; when you connect all the sections, a request flows through one piece and into the next.
定义 这种安排(即一个中间件可以调用另一个中间件,而另一个中间件又可以调用另一个中间件,依此类推)称为管道。您可以将每个中间件视为一段管道;当您连接所有部分时,请求将流经一个部分并进入下一个部分。

One of the most common use cases for middleware is for the cross-cutting concerns of your application. These aspects of your application need to occur for every request, regardless of the specific path in the request or the resource requested, including

中间件最常见的用例之一是应用程序的横切关注点。无论请求中的具体路径或请求的资源如何,每个请求都需要出现应用程序的这些方面,包括

  • Logging each request
    记录每个请求

  • Adding standard security headers to the response
    向响应添加标准安全标头

  • Associating a request with the relevant user
    将请求与相关用户关联

  • Setting the language for the current request
    设置当前请求的语言

In each of these examples, the middleware receives a request, modifies it, and then passes the request on to the next piece of middleware in the pipeline. Subsequent middleware could use the details added by the earlier middleware to handle the request in some way. In figure 4.2, for example, the authentication middleware associates the request with a user. Then the authorization middleware uses this detail to verify whether the user has permission to make that specific request to the application.

在上述每个示例中,中间件都会接收请求,对其进行修改,然后将请求传递到管道中的下一个中间件。后续中间件可以使用早期中间件添加的详细信息以某种方式处理请求。例如,在图 4.2 中,身份验证中间件将请求与用户相关联。然后,授权中间件使用此详细信息来验证用户是否有权向应用程序发出该特定请求。

alt text

Figure 4.2 Example of a middleware component modifying a request for use later in the pipeline. Middleware can also short-circuit the pipeline, returning a response before the request reaches later middleware.
图 4.2 中间件组件修改请求以供稍后在管道中使用的示例。中间件还可以使管道短路,在请求到达后面的中间件之前返回响应。

If the user has permission, the authorization middleware passes the request on to the endpoint middleware to allow it to generate a response. If the user doesn’t have permission, the authorization middleware can short-circuit the pipeline, generating a response directly; it returns the response to the previous middleware, and the endpoint middleware never sees the request. This scenario is an example of the chain-of-responsibility design pattern.

如果用户具有权限,则授权中间件会将请求传递给终端节点中间件,以允许其生成响应。如果用户没有权限,授权中间件可以使管道短路,直接生成响应;它将响应返回给前面的中间件,而 Endpoint 中间件永远不会看到该请求。此方案是责任链设计模式的一个示例。

Definition When a middleware component short-circuits the pipeline and returns a response, it’s called terminal middleware.
定义 当中间件组件使管道短路并返回响应时,它称为终端中间件。

A key point to glean from this example is that the pipeline is bidirectional. The request passes through the pipeline in one direction until a piece of middleware generates a response, at which point the response passes back through the pipeline, passing through each piece of middleware a second time, in reverse order, until it gets back to the first piece of middleware. Finally, the first/last piece of middleware passes the response back to the ASP.NET Core web server.

从这个例子中可以了解到的一个关键点是管道是双向的。请求沿一个方向通过管道,直到一个中间件生成响应,此时响应通过管道传回,以相反的顺序第二次通过每个中间件,直到它返回到第一个中间件。最后,第一个/最后一个中间件将响应传递回 ASP.NET Core Web 服务器。

The HttpContext object
对象HttpContext
I mentioned the HttpContext in chapter 3, and it’s sitting behind the scenes here too. The ASP.NET Core web server constructs an HttpContext for each request, which the ASP.NET Core application uses as a sort of storage box for a single request. Anything that’s specific to this particular request and the subsequent response can be associated with and stored in it. Examples are properties of the request, request-specific services, data that’s been loaded, or errors that have occurred. The web server fills the initial HttpContext with details of the original HTTP request and other configuration details, and then passes it on to the middleware pipeline and the rest of the application.
我在第 3 章中提到了HttpContext,它也位于幕后。ASP.NET Core Web 服务器为每个请求构建一个,ASP.NET Core 应用程序将其用作单个请求的存储盒。特定于此特定请求和后续响应的任何内容都可以与该请求相关联并存储在其中。示例包括请求的属性、特定于请求的服务、已加载的数据或发生的错误。Web 服务器使用原始 HTTP 请求的详细信息和其他配置详细信息填充初始请求,然后将其传递给中间件管道和应用程序的其余部分。HttpContextHttpContextHttpContext
All middleware has access to the HttpContext for a request. It can use this object to determine whether the request contains any user credentials, to identify which page the request is attempting to access, and to fetch any posted data, for example. Then it can use these details to determine how to handle the request.
所有中间件都可以访问 for a request.例如,它可以使用此对象来确定请求是否包含任何用户凭证,确定请求尝试访问的页面,以及获取任何已发布的数据。然后,它可以使用这些详细信息来确定如何处理请求。
When the application finishes processing the request, it updates the HttpContext with an appropriate response and returns it through the middleware pipeline to the web server. Then the ASP.NET Core web server converts the representation to a raw HTTP response and sends it back to the reverse proxy, which forwards it to the user’s browser.
当应用程序完成对请求的处理后,它会使用适当的响应进行更新,并通过中间件管道将其返回到 Web 服务器。然后,ASP.NET Core Web 服务器将表示形式转换为原始 HTTP 响应,并将其发送回反向代理,反向代理将其转发到用户的浏览器。

As you saw in chapter 3, you define the middleware pipeline in code as part of your initial application configuration in Program.cs. You can tailor the middleware pipeline specifically to your needs; simple apps may need only a short pipeline, whereas large apps with a variety of features may use much more middleware. Middleware is the fundamental source of behavior in your application. Ultimately, the middleware pipeline is responsible for responding to any HTTP requests it receives.

正如您在第 3 章中看到的,您在代码中定义了中间件管道,作为 Program.cs 中初始应用程序配置的一部分。您可以根据您的需求专门定制中间件管道;简单的应用程序可能只需要一个短的管道,而具有各种功能的大型应用程序可能会使用更多的中间件。中间件是应用程序中行为的基本来源。最终,中间件管道负责响应它收到的任何 HTTP 请求。

Requests are passed to the middleware pipeline as HttpContext objects. As you saw in chapter 3, the ASP.NET Core web server builds an HttpContext object from an incoming request, which passes up and down the middleware pipeline. When you’re using existing middleware to build a pipeline, this detail is one that you’ll rarely have to deal with. But as you’ll see in the final section of this chapter, its presence behind the scenes provides a route to exerting extra control over your middleware pipeline.

请求作为对象传递到中间件管道。正如您在第 3 章中所看到的,ASP.NET Core Web 服务器从传入请求构建一个对象,该请求在中间件管道中上下传递。当您使用现有中间件构建管道时,您很少需要处理这些细节。但正如您将在本章的最后一部分中看到的那样,它在幕后的存在为对中间件管道施加额外控制提供了一条途径。

You can also think of your middleware pipeline as being a series of concentric components, similar to a traditional matryoshka (Russian) doll, as shown in figure 4.3. A request progresses through the pipeline by heading deeper into the stack of middleware until a response is returned. Then the response returns through the middleware, passing through the components in reverse order from the request.

您还可以将中间件管道视为一系列同心组件,类似于传统的俄罗斯套娃,如图 4.3 所示。请求通过更深入地进入中间件堆栈在管道中前进,直到返回响应。然后,响应通过中间件返回,以与请求相反的顺序传递组件。

alt text

Figure 4.3 You can also think of middleware as being a series of nested components; a request is sent deeper into the middleware, and the response resurfaces from it. Each middleware component can execute logic before passing the response on to the next middleware component and can execute logic after the response has been created, on the way back out of the stack.
图 4.3 你也可以把 middleware 看作是一系列嵌套的组件;请求被发送到中间件的更深处,响应从中重新出现。每个中间件组件都可以在将响应传递到下一个中间件组件之前执行逻辑,并且可以在创建响应后执行逻辑,在返回堆栈的途中执行逻辑。

Middleware vs. HTTP modules and HTTP handlers
中间件与 HTTP 模块和 HTTP 处理程序
In the previous version of ASP.NET, the concept of a middleware pipeline isn’t used. Instead, you have HTTP modules and HTTP handlers.
在早期版本的 ASP.NET 中,未使用中间件管道的概念。相反,您有 HTTP 模块和 HTTP 处理程序。
An HTTP handler is a process that runs in response to a request and generates the response. The ASP.NET page handler, for example, runs in response to requests for .aspx pages. Alternatively, you could write a custom handler that returns resized images when an image is requested.
HTTP 处理程序是为响应请求而运行并生成响应的进程。例如,ASP.NET 页处理程序在响应对 .aspx 页的请求时运行。或者,您可以编写一个自定义处理程序,在请求图像时返回调整大小的图像。
HTTP modules handle the cross-cutting concerns of applications, such as security, logging, and session management. They run in response to the life-cycle events that a request progresses through when it’s received by the server. Examples of events include BeginRequest, AcquireRequestState, and PostAcquireRequestState.
HTTP 模块处理应用程序的横切关注点,例如安全性、日志记录和会话管理。它们运行以响应服务器收到请求时所经历的生命周期事件。事件的示例包括BeginRequest, AcquireRequestState, 和 PostAcquireRequestState.
This approach works, but sometimes it’s tricky to reason about which modules will run at which points. Implementing a module requires relatively detailed understanding of the state of the request at each individual life-cycle event.
这种方法有效,但有时很难推断哪些模块将在哪些点运行。实现模块需要相对详细地了解每个生命周期事件中的请求状态。
The middleware pipeline makes understanding your application far simpler. The pipeline is defined completely in code, specifying which components should run and in which order. Behind the scenes, the middleware pipeline in ASP.NET Core is simply a chain of method calls, with each middleware function calling the next in the pipeline.
中间件管道使理解您的应用程序变得更加简单。管道完全在代码中定义,指定哪些组件应运行以及运行顺序。在幕后,ASP.NET Core 中的中间件管道只是一个方法调用链,每个中间件函数都调用管道中的下一个。

That’s pretty much all there is to the concept of middleware. In the next section, I’ll discuss ways you can combine middleware components to create an application and how to use middleware to separate the concerns of your application.

这几乎就是中间件概念的全部内容。在下一节中,我将讨论组合中间件组件来创建应用程序的方法,以及如何使用中间件来分离应用程序的关注点。

4.2 Combining middleware in a pipeline

4.2 将中间件组合到一个管道中

Generally speaking, each middleware component has a single primary concern; it handles only one aspect of a request. Logging middleware deals only with logging the request, authentication middleware is concerned only with identifying the current user, and static-file middleware is concerned only with returning static files.

一般来说,每个中间件组件都有一个主要关注点;它只处理请求的一个方面。日志记录中间件只处理记录请求,身份验证中间件只关心识别当前用户,静态文件中间件只关心返回静态文件。

Each of these concerns is highly focused, which makes the components themselves small and easy to reason about. This approach also gives your app added flexibility. Adding static-file middleware, for example, doesn’t mean you’re forced to have image-resizing behavior or authentication; each of these features is an additional piece of middleware.

这些关注点中的每一个都是高度集中的,这使得组件本身很小并且易于推理。此方法还为应用提供了更大的灵活性。例如,添加静态文件中间件并不意味着您被迫具有图像大小调整行为或身份验证;这些功能中的每一个都是中间件的附加部分。

To build a complete application, you compose multiple middleware components into a pipeline, as shown in section 4.1. Each middleware component has access to the original request, as well as any changes made to the HttpContext by middleware earlier in the pipeline. When a response has been generated, each middleware component can inspect and/or modify the response as it passes back through the pipeline before it’s sent to the user. This feature allows you to build complex application behaviors from small, focused components.

要构建一个完整的应用程序,您需要将多个中间件组件组合到一个管道中,如第 4.1 节所示。每个中间件组件都可以访问原始请求,以及管道中较早时对 by 中间件所做的任何更改。生成响应后,每个中间件组件都可以在响应通过管道传回响应之前检查和/或修改响应,然后再将其发送给用户。此功能允许您从小型、专注的组件构建复杂的应用程序行为。

In the rest of this section, you’ll see how to create a middleware pipeline by combining various middleware components. Using standard middleware components, you’ll learn to create a holding page and to serve static files from a folder on disk. Finally, you’ll take a look at a more complex pipeline such as you’d get in a minimal API application with multiple middleware, routing, and endpoints.

在本节的其余部分,您将了解如何通过组合各种中间件组件来创建中间件管道。使用标准中间件组件,您将学习如何创建保留页并从磁盘上的文件夹中提供静态文件。最后,您将了解更复杂的管道,例如在具有多个中间件、路由和终端节点的最小 API 应用程序中获得的管道。

4.2.1 Simple pipeline scenario 1: A holding page

4.2.1 简单管道场景 1:一个保持页

For your first app in this chapter and your first middleware pipeline, you’ll learn how to create an app consisting of a holding page. Adding a holding page can be useful occasionally when you’re setting up your application to ensure that it’s processing requests without errors.

对于本章中的第一个应用程序和第一个中间件管道,您将学习如何创建由保持页面组成的应用程序。在设置应用程序时,添加保留页有时可能很有用,以确保它处理请求时没有错误。

Tip Remember that you can view the application code for this book in the GitHub repository at http://mng.bz/Y1qN.
提示 请记住,您可以在 http://mng.bz/Y1qN 的 GitHub 存储库中查看本书的应用程序代码。

In previous chapters, I mentioned that the ASP.NET Core framework is composed of many small individual libraries. You typically add a piece of middleware by referencing a package in your application’s .csproj project file and configuring the middleware in Program.cs. Microsoft ships many standard middleware components with ASP.NET Core for you to choose among; you can also use third-party components from NuGet and GitHub, or you can build your own custom middleware. You can find the list of built-in middleware at http://mng.bz/Gyxq.

在前面的章节中,我提到了 ASP.NET Core 框架由许多小型的单个库组成。通常,您可以通过引用应用程序的 .csproj 项目文件中的包并在 Program.cs 中配置中间件来添加中间件。Microsoft Core 附带了许多标准中间件组件 ASP.NET 供您选择;您还可以使用 NuGet 和 GitHub 中的第三方组件,也可以构建自己的自定义中间件。您可以在 http://mng.bz/Gyxq 中找到内置中间件的列表。

Note I discuss building custom middleware in chapter 31.
注意 我在第 31 章中讨论了构建自定义中间件。

In this section, you’ll see how to create one of the simplest middleware pipelines, consisting only of WelcomePageMiddleware. WelcomePageMiddleware is designed to provide a sample HTML page quickly when you’re first developing an application, as you can see in figure 4.4. You wouldn’t use it in a production app, as you can’t customize the output, but it’s a single, self-contained middleware component you can use to ensure that your application is running correctly.

在本节中,您将了解如何创建最简单的中间件管道之一,其中仅由WelcomePageMiddleware. WelcomePageMiddleware旨在在您首次开发应用程序时快速提供示例 HTML 页面,如图 4.4 所示。您不会在生产应用程序中使用它,因为您无法自定义输出,但它是一个独立的中间件组件,您可以使用它来确保您的应用程序正确运行。

alt text

Figure 4.4 The Welcome-page middleware response. Every request to the application, at any path, will return the same Welcome-page response.
图 4.4 Welcome-page 中间件响应。在任何路径上对应用程序的每个请求都将返回相同的 Welcome-page 响应。

Tip WelcomePageMiddleware is included as part of the base ASP.NET Core framework, so you don’t need to add a reference to any additional NuGet packages.
提示 WelcomePageMiddleware作为基本 ASP.NET Core 框架的一部分包含在内,因此无需添加对任何其他 NuGet 包的引用。

Even though this application is simple, the same process you’ve seen before occurs when the application receives an HTTP request, as shown in figure 4.5.

尽管此应用程序很简单,但当应用程序收到 HTTP 请求时,您之前看到的相同过程也会发生,如图 4.5 所示。
alt text

Figure 4.5 WelcomePageMiddleware handles a request. The request passes from the reverse proxy to the ASP.NET Core web server and finally to the middleware pipeline, which generates an HTML response.
图 4.5 WelcomePageMiddleware处理请求。请求从反向代理传递到 ASP.NET Core Web 服务器,最后传递到中间件管道,中间件管道会生成 HTML 响应。

The request passes to the ASP.NET Core web server, which builds a representation of the request and passes it to the middleware pipeline. As it’s the first (only!) middleware in the pipeline, WelcomePageMiddleware receives the request and must decide how to handle it. The middleware responds by generating an HTML response, no matter what request it receives. This response passes back to the ASP.NET Core web server, which forwards it to the reverse proxy and then to the user to display in their browser.

请求传递到 ASP.NET Core Web 服务器,该服务器构建请求的表示形式并将其传递给中间件管道。因为它是管道中的第一个(唯一的)中间件,所以WelcomePageMiddleware接收请求并且必须决定如何处理它。中间件通过生成 HTML 响应来响应,无论它收到什么请求。此响应将传递回 ASP.NET Core Web 服务器,该服务器将其转发到反向代理,然后转发给用户以在其浏览器中显示。

As with all ASP.NET Core applications, you define the middleware pipeline in Program.cs by calling Use* methods on the WebApplication instance. To create your first middleware pipeline, which consists of a single middleware component, you need a single method call. The application doesn’t need any extra configuration or services, so your whole application consists of the four lines in the following listing.

与所有 ASP.NET Core 应用程序一样,您可以通过调用实例上的方法在 Program.cs 中定义中间件管道。要创建您的第一个中间件管道(由单个中间件组件组成),您需要一个方法调用。该应用程序不需要任何额外的配置或服务,因此您的整个应用程序由以下清单中的 4 行组成。

Listing 4.1 Program.cs for a Welcome-page middleware pipeline
列表 4.1 Welcome-page 中间件管道的 Program.cs

WebApplicationBuilder builder = WebApplication.CreateBuilder(args); ❶
WebApplication app = builder.Build(); ❶
app.UseWelcomePage(); ❷
app.Run(); ❸

❶ Uses the default WebApplication configuration
使用默认的 WebApplication 配置
❷ The only custom middleware in the pipeline
管道中唯一的自定义中间件
❸ Runs the application to handle requests
运行应用程序以处理请求

You build up the middleware pipeline in ASP.NET Core by calling methods on WebApplication (which implements IApplicationBuilder). WebApplication doesn’t define methods like UseWelcomePage itself; instead, these are extension methods.

您可以通过调用 WebApplication(实现 IApplicationBuilder)上的方法,在 ASP.NET Core 中构建中间件管道。WebApplication 不定义 UseWelcomePage 本身等方法;相反,这些是扩展方法。

Using extension methods allows you to add functionality to the WebApplication class, while keeping the implementation isolated from it. Under the hood, the methods typically call another extension method to add the middleware to the pipeline. Behind the scenes, for example, the UseWelcomePage method adds the WelcomePageMiddleware to the pipeline by calling
使用扩展方法可以向 WebApplication 类添加功能,同时保持实现与该类隔离。在后台,这些方法通常会调用另一个扩展方法以将中间件添加到管道中。例如,在后台,UseWelcomePage 方法通过调用

UseMiddleware<WelcomePageMiddleware>();

This convention of creating an extension method for each piece of middleware and starting the method name with Use is designed to improve discoverability when you add middleware to your application.[2] ASP.NET Core includes a lot of middleware as part of the core framework, so you can use IntelliSense in Visual Studio and other integrated development environments (IDEs) to view all the middleware that’s available, as shown in figure 4.6.

这种为每个中间件创建一个扩展方法并以 Use 开头的方法名称的约定旨在提高向应用程序添加中间件时的可发现性。2 ASP.NET Core 包含许多中间件作为核心框架的一部分,因此您可以在 Visual Studio 和其他集成开发环境 (IDE) 中使用 IntelliSense 来查看所有可用的中间件,如图 4.6 所示。

alt text

Figure 4.6 IntelliSense makes it easy to view all the available middleware to add to your middleware pipeline.
图 4.6 使用 IntelliSense 可以轻松查看要添加到中间件管道的所有可用中间件。

Calling the UseWelcomePage method adds the WelcomePageMiddleware as the next middleware in the pipeline. Although you’re using only a single middleware component here, it’s important to remember that the order in which you make calls to IApplicationBuilder in Configure defines the order in which the middleware will run in the pipeline.

调用 UseWelcomePage 方法会将 WelcomePageMiddleware 添加为管道中的下一个中间件。尽管您在此处只使用单个中间件组件,但请务必记住,您在 Configure 中调用 IApplicationBuilder 的顺序定义了中间件在管道中运行的顺序。

Warning When you’re adding middleware to the pipeline, always take care to consider the order in which it will run. A component can access only data created by middleware that comes before it in the pipeline.
警告 当您将中间件添加到管道时,请始终注意考虑它的运行顺序。组件只能访问管道中位于其前面的中间件创建的数据。

This application is the most basic kind, returning the same response no matter which URL you navigate to, but it shows how easy it is to define your application behavior with middleware. Next, we’ll make things a little more interesting by returning different responses when you make requests to different paths.

此应用程序是最基本的类型,无论您导航到哪个 URL,它都会返回相同的响应,但它显示了使用中间件定义应用程序行为是多么容易。接下来,当您向不同的路径发出请求时,我们将返回不同的响应,从而使事情变得更有趣。

4.2.2 Simple pipeline scenario 2: Handling static files

4.2.2 简单管道场景 2:处理静态文件

In this section, I’ll show you how to create one of the simplest middleware pipelines you can use for a full application: a static-file application. Most web applications, including those with dynamic content, serve some pages by using static files. Images, JavaScript, and CSS stylesheets are normally saved to disk during development and are served up when requested from the special wwwroot folder of your project, normally as part of a full HTML page request.

在本节中,我将向您展示如何创建可用于完整应用程序的最简单的中间件管道之一:静态文件应用程序。大多数 Web 应用程序(包括具有动态内容的应用程序)都使用静态文件来提供某些页面。图像、JavaScript 和 CSS 样式表通常在开发过程中保存到磁盘中,并在从项目的特殊 wwwroot 文件夹请求时提供,通常作为完整 HTML 页面请求的一部分。

Definition By default, the wwwroot folder is the only folder in your application that ASP.NET Core will serve files from. It doesn’t serve files from other folders for security reasons. The wwwroot folder in an ASP.NET Core project is typically deployed as is to production, including all the files and folders it contains.
定义 默认情况下,wwwroot 文件夹是应用程序中 ASP.NET Core 将从中提供文件的唯一文件夹。出于安全原因,它不会提供来自其他文件夹的文件。ASP.NET Core 项目中的 wwwroot 文件夹通常按原样部署到生产环境,包括它包含的所有文件和文件夹。

You can use StaticFileMiddleware to serve static files from the wwwroot folder when requested, as shown in figure 4.7. In this example, an image called moon.jpg exists in the wwwroot folder. When you request the file using the /moon.jpg path, it’s loaded and returned as the response to the request.
当请求时,您可以使用 wwwroot 文件夹中的静态文件,如图 4.7 所示。在此示例中,名为 moon.jpg 的映像存在于 wwwroot 文件夹中。当您使用路径请求文件StaticFileMiddleware/moon.jpg时,该文件将加载并作为对请求的响应返回。

alt text

Figure 4.7 Serving a static image file using the static-file middleware
图 4.7 使用 static-file 中间件提供静态图像文件

If the user requests a file that doesn’t exist in the wwwroot folder, such as missing.jpg, the static-file middleware won’t serve a file. Instead, a 404 HTTP error code response will be sent to the user’s browser, which displays its default “File Not Found” page, as shown in figure 4.8.

如果用户请求 wwwroot 文件夹中不存在的文件(如 missing.jpg),则静态文件中间件不会提供文件。相反,404 HTTP 错误代码响应将发送到用户的浏览器,该浏览器显示其默认的 “File Not Found” 页面,如图 4.8 所示。

alt text

Figure 4.8 Returning a 404 to the browser when a file doesn’t exist. The requested file didn’t exist in the wwwroot folder, so the ASP.NET Core application returned a 404 response. Then the browser (Microsoft Edge, in this case) show the user a default “File Not Found” error pager
图 4.8 当文件不存在时向浏览器返回 404。请求的文件在 wwwroot 文件夹中不存在,因此 ASP.NET Core 应用程序返回 404 响应。然后,浏览器(在本例中为 Microsoft Edge)向用户显示默认的“找不到文件”错误分页程序

Note How this page looks depends on your browser. In some browsers, you may see a blank page.
注意 此页面的外观取决于您的浏览器。在某些浏览器中,您可能会看到一个空白页面。

Building the middleware pipeline for this simple static-file application is easy. The pipeline consists of a single piece of middleware, StaticFileMiddleware, as you can see in the following listing. You don’t need any services, so configuring the middleware pipeline with UseStaticFiles is all that’s required.

为这个简单的静态文件应用程序构建中间件管道很容易。该管道由一个中间件StaticFileMiddleware 组成,如下面的清单所示。您不需要任何服务,因此使用 UseStaticFiles 配置中间件管道就是全部需要的。

Listing 4.2 Program.cs for a static-file middleware pipeline
列表 4.2 静态文件中间件管道的 Program.cs

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();
app.UseStaticFiles(); ❶
app.Run();

❶ Adds the StaticFileMiddleware to the pipeline
将 StaticFileMiddleware 添加到管道中

Tip Remember that you can view the application code for this book in the GitHub repository at http://mng.bz/Y1qN.
提示 记住,您可以在 http://mng.bz/Y1qN 的 GitHub 存储库中查看本书的应用程序代码。

When the application receives a request, the ASP.NET Core web server handles it and passes it to the middleware pipeline. StaticFileMiddleware receives the request and determines whether it can handle it. If the requested file exists, the middleware handles the request and returns the file as the response, as shown in figure 4.9.

当应用程序收到请求时,ASP.NET Core Web 服务器会处理该请求并将其传递给中间件管道。StaticFileMiddleware 接收请求并确定它是否可以处理它。如果请求的文件存在,中间件会处理请求并返回该文件作为响应,如图 4.9 所示。

alt text

Figure 4.9 StaticFileMiddleware handles a request for a file. The middleware checks the wwwroot folder to see if whether requested moon.jpg file exists. The file exists, so the middleware retrieves it and returns it as the response to the web server and, ultimately, to the browser.
图 4.9 StaticFileMiddleware 处理文件请求。中间件检查 wwwroot 文件夹以查看请求的 moon.jpg 文件是否存在。该文件存在,因此中间件会检索它并将其作为响应返回给 Web 服务器,并最终返回给浏览器。

If the file doesn’t exist, the request effectively passes through the static-file middleware unchanged. But wait—you added only one piece of middleware, right? Surely you can’t pass the request through to the next middleware component if there isn’t another one.

如果文件不存在,则请求将原封不动地有效地通过 static-file 中间件。但是等等 — 您只添加了一个中间件,对吧?当然,如果没有另一个中间件组件,你就不能将请求传递给下一个中间件组件。

ASP.NET Core automatically adds a dummy piece of middleware to the end of the pipeline. This middleware always returns a 404 response if it’s called.

ASP.NET Core 会自动将一个虚拟中间件添加到管道的末尾。如果调用此中间件,则始终返回 404 响应。

Tip If no middleware generates a response for a request, the pipeline automatically returns a simple 404 error response to the browser.
提示 如果没有中间件为请求生成响应,则管道会自动向浏览器返回简单的 404 错误响应。

HTTP response status codes
HTTP 响应状态代码
Every HTTP response contains a status code and, optionally, a reason phrase describing the status code. Status codes are fundamental to the HTTP protocol and are a standardized way of indicating common results. A 200 response, for example, means that the request was successfully answered, whereas a 404 response indicates that the resource requested couldn’t be found. You can see the full list of standardized status codes at https://www.rfc-editor.org/rfc/rfc9110#name-status-codes.
每个 HTTP 响应都包含一个状态代码和一个描述状态代码的原因短语(可选)。状态代码是 HTTP 协议的基础,是表示常见结果的标准化方式。例如,200 响应表示请求已成功响应,而 404 响应表示找不到请求的资源。您可以在 https://www.rfc-editor.org/rfc/rfc9110#name-status-codes 上查看标准化状态代码的完整列表。
Status codes are always three digits long and are grouped in five classes, based on the first digit:
状态代码始终为三位数,并根据第一位数字分为五类:
· 1xx—Information. This code is not often used; it provides a general acknowledgment.
1xx - 信息。此代码不经常使用;它提供了一个一般性的确认。
· 2xx—Success. The request was successfully handled and processed.
2xx - 成功。已成功处理和处理请求。
· 3xx—Redirection. The browser must follow the provided link to allow the user to log in, for example.
3xx — 重定向。例如,浏览器必须按照提供的链接允许用户登录。
· 4xx—Client error. A problem occurred with the request. The request sent invalid data, for example, or the user isn’t authorized to perform the request.
4xx — 客户端错误。请求出现问题。例如,请求发送了无效数据,或者用户无权执行请求。
· 5xx—Server error. A problem on the server caused the request to fail.
5xx — 服务器错误。服务器上的问题导致请求失败。
These status codes typically drive the behavior of a user’s browser. The browser will handle a 301 response automatically, for example, by redirecting to the provided new link and making a second request, all without the user’s interaction.
这些状态代码通常驱动用户浏览器的行为。浏览器将自动处理 301 响应,例如,重定向到提供的新链接并发出第二个请求,所有这些都无需用户交互。
Error codes are in the 4xx and 5xx classes. Common codes include a 404 response when a file couldn’t be found, a 400 error when a client sends invalid data (such as an invalid email address), and a 500 error when an error occurs on the server. HTTP responses for error codes may include a response body, which is content to display when the client receives the response.
错误代码位于 4xx 和 5xx 类中。常见代码包括找不到文件时的 404 响应、客户端发送无效数据(例如无效的电子邮件地址)时的 400 错误,以及服务器上发生错误时的 500 错误。错误代码的 HTTP 响应可能包括响应正文,该正文是客户端收到响应时要显示的内容。

This basic ASP.NET Core application makes it easy to see the behavior of the ASP.NET Core middleware pipeline and the static-file middleware in particular, but it’s unlikely that your applications will be this simple. It’s more likely that static files will form one part of your middleware pipeline. In the next section you’ll see how to combine multiple middleware components as we look at a simple minimal API application.

这个基本的 ASP.NET Core 应用程序可以轻松查看 ASP.NET Core 中间件管道的行为,尤其是静态文件中间件,但您的应用程序不太可能如此简单。静态文件更有可能构成中间件管道的一部分。在下一节中,您将看到如何组合多个中间件组件,因为我们将了解一个简单的最小 API 应用程序。

4.2.3 Simple pipeline scenario 3: A minimal API application

4.2.3 简单管道场景 3:一个最小的 API 应用程序

By this point, you should have a decent grasp of the middleware pipeline, insofar as you understand that it defines your application’s behavior. In this section you’ll see how to combine several standard middleware components to form a pipeline. As before, you do this in Program.cs by adding middleware to the WebApplication object.

此时,您应该对中间件管道有一定的了解,只要您了解它定义了应用程序的行为。在本节中,您将了解如何组合多个标准中间件组件以形成一个管道。和以前一样,您可以通过将中间件添加到 WebApplication 对象来在 Program.cs 中执行此作。

You’ll begin by creating a basic middleware pipeline that you’d find in a typical ASP.NET Core minimal APIs template and then extend it by adding middleware. Figure 4.10 shows the output you see when you navigate to the home page of the application—identical to the sample application in chapter 3.

首先,您将创建一个基本的中间件管道,该管道可以在典型的 ASP.NET Core 最小 API 模板中找到,然后通过添加中间件来扩展它。图 4.10 显示了导航到应用程序主页时看到的输出,与第 3 章中的示例应用程序相同。

alt text

Figure 4.10 A simple minimal API application. The application uses only four pieces of middleware: routing middleware to choose the endpoint to run, endpoint middleware to generate the response from a Razor Page, static-file middleware to serve image files, and exception-handler middleware to capture any errors.
图 4.10 一个简单的最小 API 应用程序。应用程序只使用四个中间件:路由中间件来选择要运行的端点,endpoint 中间件(用于从 Razor 页面生成响应)、静态文件中间件(用于提供图像文件)和异常处理程序中间件(用于捕获任何错误)。

Creating this application requires only four pieces of middleware: routing middleware to choose a minimal API endpoint to execute, endpoint middleware to generate the response, static-file middleware to serve any image files from the wwwroot folder, and exception-handler middleware to handle any errors that might occur. Even though this example is still a Hello World! example, this architecture is much closer to a realistic example. The following listing shows an example of such an application.

创建此应用程序只需要四个中间件:用于选择要执行的最小 API 终端节点的路由中间件、用于生成响应的终端节点中间件、用于提供 wwwroot 文件夹中任何图像文件的静态文件中间件,以及用于处理可能发生的任何错误的异常处理程序中间件。即使此示例仍然是 Hello World! example,此体系结构更接近于实际示例。下面的清单显示了此类应用程序的一个示例。

Listing 4.3 A basic middleware pipeline for a minimal APIs application
清单 4.3 用于最小 API 应用程序的基本中间件管道

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();
UseDeveloperExceptionPage(); ❶
app.UseStaticFiles(); ❷
app.UseRouting(); ❸
app.MapGet("/", () => "Hello World!"); ❹
app.Run();

❶ This call isn’t strictly necessary, as it’s already added by WebApplication by default.
此调用并非绝对必要,因为默认情况下它已由 WebApplication 添加。

❷ Adds the StaticFileMiddleware to the pipeline
将 StaticFileMiddleware 添加到管道中

❸ Adds the RoutingMiddleware to the pipeline
将 RoutingMiddleware 添加到管道中

❹ Defines an endpoint for the application
为应用程序定义端点

The addition of middleware to WebApplication to form the pipeline should be familiar to you now, but several points are worth noting in this example:
您现在应该很熟悉向 WebApplication 添加中间件以形成管道,但在此示例中,有几点值得注意:

  • Middleware is added with Use() methods.
    中间件是使用 Use
    () 方法添加的。
  • MapGet defines an endpoint, not middleware. It defines the endpoints that the routing and endpoint middleware can use.
    MapGet 定义端点,而不是中间件。它定义路由和终端节点中间件可以使用的终端节点。
  • WebApplication automatically adds some middleware to the pipeline, such as the EndpointMiddleware.
    WebApplication 会自动向管道中添加一些中间件,例如 EndpointMiddleware。
  • The order of the Use() method calls is important and defines the order of the middleware pipeline.
    Use
    () 方法调用的顺序很重要,它定义了中间件管道的顺序。

First, all the methods for adding middleware start with Use. As I mentioned earlier, this is thanks to the convention of using extension methods to extend the functionality of WebApplication; prefixing the methods with Use should make them easier to discover.

首先,添加中间件的所有方法都以 Use 开头。正如我前面提到的,这要归功于使用扩展方法来扩展 WebApplication 功能的约定;为方法添加前缀 Use 应该会使它们更容易被发现。

Second, it’s important to understand that the MapGet method does not add middleware to the pipeline; it defines an endpoint in your application. These endpoints are used by the routing and endpoint middleware. You’ll learn more about endpoints and routing in chapter 5.

其次,请务必了解 MapGet 方法不会将中间件添加到管道中;它定义应用程序中的终端节点。这些终端节点由路由和终端节点中间件使用。您将在第 5 章中了解有关终端节点和路由的更多信息。

Tip You can define the endpoints for your app by using MapGet() anywhere in Program.cs before the call to app.Run(), but the calls are typically placed after the middleware pipeline definition.
提示 您可以使用MapGet() Program.cs在调用应用程序。Run(),但调用通常放在中间件管道定义之后。

In chapter 3, I mentioned that WebApplication automatically adds middleware to your app. You can see this process in action in listing 4.3 automatically adding the EndpointMiddleware to the end of the middleware pipeline. WebApplication also automatically adds the developer exception page middleware to the start of the middleware pipeline when you’re running in development. As a result, you can omit the call to UseDeveloperExceptionPage() from listing 4.3, and your middleware pipeline will be essentially the same.

在第 3 章中,我提到了 WebApplication 会自动将中间件添加到您的应用程序中。你可以在清单 4.3 中看到这个过程的实际效果,它自动将 EndpointMiddleware 添加到中间件管道的末尾。当您在开发中运行时,WebApplication 还会自动将开发人员异常页面中间件添加到中间件管道的开头。因此,您可以省略清单 4.3 中对 UseDeveloperExceptionPage() 的调用,您的中间件管道将基本相同。

WebApplication and autoadded middleware
WebApplication 和自动添加的中间件
WebApplication and WebApplicationBuilder were introduced in .NET 6 to try to reduce the amount of boilerplate code required for a Hello World! ASP.NET Core application. As part of this initiative, Microsoft chose to have WebApplication automatically add various middleware to the pipeline. This decision alleviates some of the common getting-started pain points of middleware ordering by ensuring that, for example, UseRouting() is always called before UseAuthorization().
WebApplication 和 WebApplicationBuilder 是在 .NET 6 中引入的,旨在尝试减少 "Hello World" ASP.NET Core 应用程序所需的样板代码量!。作为该计划的一部分,Microsoft 选择让 WebApplication 自动将各种中间件添加到管道中。此决定通过确保(例如)始终在 UseAuthorization() 之前调用 UseRouting(),缓解了中间件排序的一些常见入门痛点。
Everything has trade-offs, of course, and for WebApplication the trade-off is that it’s harder to understand exactly what’s in your middleware pipeline without having deep knowledge of the framework code itself.
当然,一切都有权衡,对于 WebApplication 来说,权衡是,如果不深入了解框架代码本身,就更难准确理解中间件管道中的内容。
Luckily, you don’t need to worry about the middleware that WebApplication adds for the most part. If you’re new to ASP.NET Core, generally you can accept that WebApplication will add the middleware only when it’s necessary and safe to do so.
幸运的是,您无需担心WebApplication 在很大程度上增加了。如果您是 ASP.NET Core 的新用户,通常,你可以接受 WebApplication 仅在必要且安全的情况下才会添加中间件。
Nevertheless, in some cases it may pay to know exactly what’s in your pipeline, especially if you’re familiar with ASP.NET Core. In .NET 7, WebApplication automatically adds some or all of the following middleware to the start of the middleware pipeline:
不过,在某些情况下,确切地了解你的管道中的内容可能是值得的,特别是如果你熟悉 ASP.NET Core。在 .NET 7 中,WebApplication 会自动将以下部分或全部中间件添加到中间件管道的开头:
· HostFilteringMiddleware—This middleware is security-related. You can read more about why it’s useful and how to configure it at http://mng.bz/zXxa.
HostFilteringMiddleware — 此中间件与安全性相关。您可以在 http://mng.bz/zXxa 中阅读有关它为何有用以及如何配置它的更多信息。
· ForwardedHeadersMiddleware—This middleware controls how forwarded headers are handled. You can read more about it in chapter 27.
ForwardedHeadersMiddleware — 此中间件控制如何处理转发的标头。您可以在第 27 章中阅读更多相关信息。
· DeveloperExceptionPageMiddleware—As already discussed, this middleware is added when you run in a development environment.
DeveloperExceptionPageMiddleware — 如前所述,当您在开发环境中运行时,会添加此中间件。
· RoutingMiddleware—If you add any endpoints to your application, UseRouting() runs before you add any custom middleware to your application.
RoutingMiddleware — 如果您向应用程序添加任何终端节点,则 UseRouting() 会在您向应用程序添加任何自定义中间件之前运行。
· AuthenticationMiddleware—If you configure authentication, this middleware authenticates a user for the request. Chapter 23 discusses authentication in detail.
AuthenticationMiddleware — 如果您配置身份验证,则此中间件将对请求的用户进行身份验证。第 23 章详细讨论了身份验证。
· AuthorizationMiddleware—The authorization middleware runs after authentication and determines whether a user is permitted to execute an endpoint. If the user doesn’t have permission, the request is short-circuited. I discuss authorization in detail in chapter 24.
AuthorizationMiddleware — 授权中间件在身份验证后运行,并确定是否允许用户执行终端节点。如果用户没有权限,则请求将短路。我在第 24 章中详细讨论了授权。
· EndpointMiddleware—This middleware pairs with the RoutingMiddleware to execute an endpoint. Unlike the other middleware described here, the EndpointMiddleware is added to the end of the middleware pipeline, after any other middleware you configure in Program.cs.
EndpointMiddleware — 此中间件与 RoutingMiddleware 配对以执行终端节点。与此处描述的其他中间件不同,EndpointMiddleware 被添加到中间件管道的末尾,位于您在 Program.cs 中配置的任何其他中间件之后。
Depending on your Program.cs configuration, WebApplication may not add all this middleware. Also, if you don’t want some of this automatic middleware to be at the start of your middleware pipeline, generally you can override the location. In listing 4.3, for example, we override the automatic RoutingMiddleware location by calling UseRouting() explicitly, ensuring that routing occurs exactly where we need it.
根据您的 Program.cs 配置,WebApplication 可能不会添加所有这些中间件。此外,如果您不希望某些自动中间件位于中间件管道的开头,通常可以覆盖该位置。例如,在清单 4.3 中,我们通过显式调用 UseRouting()来覆盖自动定位RoutingMiddleware,确保路由恰好发生在我们需要的地方。

Another important point about listing 4.3 is that the order in which you add the middleware to the WebApplication object is the order in which the middleware is added to the pipeline. The order of the calls in listing 4.3 creates a pipeline similar to that shown in figure 4.11.

清单 4.3 的另一个要点是,将中间件添加到 WebApplication 对象的顺序就是将中间件添加到管道的顺序。清单 4.3 中的调用顺序将创建一个类似于图 4.11 所示的管道。

alt text

Figure 4.11 The middleware pipeline for the example application in listing 4.3. The order in which you add the middleware to WebApplication defines the order of the middleware in the pipeline.
图 4.11 清单 4.3 中示例应用程序的中间件管道。将中间件添加到 WebApplication 的顺序定义了中间件在管道中的顺序。

The ASP.NET Core web server passes the incoming request to the developer exception page middleware first. This exception-handler middleware ignores the request initially; its purpose is to catch any exceptions thrown by later middleware in the pipeline, as you’ll see in section 4.3. It’s important for this middleware to be placed early in the pipeline so that it can catch errors produced by later middleware.

ASP.NET Core Web 服务器首先将传入请求传递给开发人员异常页面中间件。此异常处理程序中间件最初会忽略请求;它的目的是捕获管道中后续 middleware 抛出的任何异常,如 4.3 节所示。将此中间件放在管道的早期非常重要,这样它就可以捕获后续中间件产生的错误。

The developer exception page middleware passes the request on to the static-file middleware. The static-file handler generates a response if the request corresponds to a file; otherwise, it passes the request on to the routing middleware. The routing middleware selects a minimal API endpoint based on the endpoints defined and the request URL, and the endpoint middleware executes the selected minimal API endpoint. If no endpoint can handle the requested URL, the automatic dummy middleware returns a 404 response.

开发人员异常页面中间件将请求传递给静态文件中间件。如果请求对应于文件,则 static-file 处理程序会生成响应;否则,它将请求传递给路由中间件。路由中间件根据定义的端点和请求 URL 选择最小 API 端点,端点中间件执行选定的最小 API 端点。如果没有终端节点可以处理请求的 URL,则自动虚拟中间件将返回 404 响应。

In chapter 3, I mentioned that WebApplication adds the RoutingMiddleware to the start of the middleware pipeline automatically. So you may be wondering why I explicitly added it to the pipeline in listing 4.3 using UseRouting().

在第 3 章中,我提到 WebApplication 会自动将 RoutingMiddleware 添加到中间件管道的开头。因此,您可能想知道为什么我使用 UseRouting() 将它显式添加到清单 4.3 中的管道中。

The answer, again, is related to the order of the middleware. Adding an explicit call to UseRouting() tells WebApplication not to add the RoutingMiddleware automatically before the middleware defined in Program.cs. This allows us to “move” the RoutingMiddleware to be placed after the StaticFileMiddleware. Although this step isn’t strictly necessary in this case, it’s good practice. The StaticFileMiddleware doesn’t use routing, so it’s preferable to let this middleware check whether the incoming request is for a static file; if so, it can short-circuit the pipeline and avoid the unnecessary call to the RoutingMiddleware.

答案同样与中间件的顺序有关。添加对 UseRouting() 的显式调用会告诉 WebApplication 不要在 Program.cs 中定义的中间件之前自动添加 RoutingMiddleware。这允许我们将 RoutingMiddleware “移动” 为放置在 StaticFileMiddleware 之后。尽管在这种情况下,此步骤并非绝对必要,但这是一种很好的做法。StaticFileMiddleware 不使用路由,因此最好让这个中间件检查传入的请求是否是针对静态文件的;如果是这样,它可以使管道短路并避免对 RoutingMiddleware 的不必要调用。

Note In versions 1.x and 2.x of ASP.NET Core, the routing and endpoint middleware were combined in a single Model-View-Controller (MVC) middleware component. Splitting the responsibilities for routing from execution makes it possible to insert middleware between the routing and endpoint middleware. I discuss routing further in chapters 6 and 14.
注意 在 ASP.NET Core 的 1.x 和 2.x 版本中,路由和端点中间件组合在一个模型-视图-控制器 (MVC) 中间件组件中。将路由的责任与执行分开,可以在路由和终端节点中间件之间插入中间件。我在第 6 章和第 14 章中进一步讨论了路由。

The impact of ordering is most obvious when you have two pieces of middleware that are listening for the same path. The endpoint middleware in the example pipeline currently responds to a request to the home page of the application (with the / path) by returning the string "Hello World!", as shown in figure 4.10. Figure 4.12 shows what happens if you reintroduce a piece of middleware that you saw previously, WelcomePageMiddleware, and configure it to respond to the / path as well.

当你有两个 middleware 正在侦听同一路径时,排序的影响最为明显。示例管道中的终端节点中间件当前通过返回字符串 “Hello World!” 来响应对应用程序主页(使用 / 路径)的请求,如图 4.10 所示。图 4.12 显示了如果你重新引入之前看到的中间件 WelcomePageMiddleware,并将其配置为也响应 / 路径会发生什么。

alt text

Figure 4.12 The Welcome-page middleware response. The Welcome-page middleware comes before the endpoint middleware, so a request to the home page returns the Welcome-page middleware instead of the minimal API response.
图 4.12 Welcome-page 中间件响应。Welcome-page 中间件位于端点中间件之前,因此对主页的请求将返回 Welcome-page 中间件,而不是最小的 API 响应。

As you saw in section 4.2.1, WelcomePageMiddleware is designed to return a fixed HTML response, so you wouldn’t use it in a production app, but it illustrates the point nicely. In the following listing, it’s added to the start of the middleware pipeline and configured to respond only to the "/" path.

正如你在 4.2.1 节中看到的,WelcomePageMiddleware 被设计为返回一个固定的 HTML 响应,所以你不会在生产应用程序中使用它,但它很好地说明了这一点。在下面的清单中,它被添加到中间件管道的开头,并配置为仅响应 “/” 路径。

Listing 4.4 Adding WelcomePageMiddleware to the pipeline
清单 4.4 添加到管道WelcomePageMiddleware

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build();
app.UseWelcomePage("/"); ❶
app.UseDeveloperExceptionPage();
app.UseStaticFiles();
app.UseRouting(); ❷
app.MapGet("/", () => "Hello World!"); ❷
app.Run();

❶ WelcomePageMiddleware handles all requests to the “/” path and returns a sample HTML response.
WelcomePageMiddleware 处理对 “/” 路径的所有请求,并返回一个示例 HTML 响应。
❷ Requests to “/” will never reach the endpoint middleware, so this endpoint won’t be called.
对 “/” 的请求永远不会到达端点中间件,因此不会调用此端点。

Even though you know that the endpoint middleware can also handle the "/" path, WelcomePageMiddleware is earlier in the pipeline, so it returns a response when it receives the request to "/", short-circuiting the pipeline, as shown in figure 4.13. None of the other middleware in the pipeline runs for the request, so none has an opportunity to generate a response.
即使您知道端点中间件也可以处理 “/” 路径,但 WelcomePageMiddleware 位于管道的早期,因此当它收到对 “/” 的请求时,它会返回响应,从而使管道短路,如图 4.13 所示。管道中的其他中间件都不会为请求运行,因此没有机会生成响应。

alt text

Figure 4.13 Overview of the application handling a request to the "/" path. The Welcome-page middleware is first in the middleware pipeline, so it receives the request before any other middleware. It generates an HTML response, short-circuiting the pipeline. No other middleware runs for the request.
图 4.13 处理对 “/” 路径请求的应用程序概述。欢迎页面middleware 在中间件管道中排在第一位,因此它会在任何其他中间件之前收到请求。它生成 HTML 响应,使管道短路。没有其他中间件为请求运行。

As WebApplication automatically adds EndpointMiddleware to the end of the middleware pipeline, the WelcomePageMiddleware will always be ahead of it, so it always generates a response before the endpoint can execute in this example.

由于 WebApplication 会自动将 EndpointMiddleware 添加到中间件管道的末尾,因此 WelcomePageMiddleware 将始终位于其前面,因此在此示例中,它总是在端点可以执行之前生成响应。

Tip You should always consider the order of middleware when adding it to WebApplication. Middleware added earlier in the pipeline will run (and potentially return a response) before middleware added later.
提示 在将中间件添加到 WebApplication 时,您应该始终考虑中间件的顺序。在管道中较早添加的中间件将在稍后添加的中间件之前运行 (并可能返回响应) 。

All the examples shown so far try to handle an incoming request and generate a response, but it’s important to remember that the middleware pipeline is bidirectional. Each middleware component gets an opportunity to handle both the incoming request and the outgoing response. The order of middleware is most important for those components that create or modify the outgoing response.

到目前为止显示的所有示例都尝试处理传入请求并生成响应,但请务必记住,中间件管道是双向的。每个中间件组件都有机会同时处理传入请求和传出响应。中间件的顺序对于创建或修改传出响应的组件最为重要。

In listing 4.3, I included DeveloperExceptionPageMiddleware at the start of the application’s middleware pipeline, but it didn’t seem to do anything. Error-handling middleware characteristically ignores the incoming request as it arrives in the pipeline; instead, it inspects the outgoing response, modifying it only when an error has occurred. In the next section, I discuss the types of error-handling middleware that are available to use with your application and when to use them.

在清单 4.3 中,我在应用程序的中间件管道的开头添加了 DeveloperExceptionPageMiddleware,但它似乎没有做任何事情。错误处理中间件通常在传入请求到达管道时忽略该请求;相反,它会检查传出响应,仅在发生错误时对其进行修改。在下一节中,我将讨论可用于应用程序的错误处理中间件的类型以及何时使用它们。

4.3 Handling errors using middleware

4.3 使用中间件处理错误

Errors are a fact of life when you’re developing applications. Even if you write perfect code, as soon as you release and deploy your application, users will find a way to break it, by accident or intentionally! The important thing is that your application handles these errors gracefully, providing a suitable response to the user and not causing your whole application to fail.

在开发应用程序时,错误是不可避免的事实。即使您编写了完美的代码,一旦您发布和部署了您的应用程序,用户也会找到一种方法来破坏它,无论是有意还是有意!重要的是,您的应用程序要妥善处理这些错误,为用户提供适当的响应,并且不会导致整个应用程序失败。

The design philosophy for ASP.NET Core is that every feature is opt-in. So because error handling is a feature, you need to enable it explicitly in your application. Many types of errors could occur in your application, and you have many ways to handle them, but in this section I focus on a single type of error: exceptions.

ASP.NET Core 的设计理念是每个功能都是可选的。因此,由于错误处理是一项功能,因此您需要在应用程序中显式启用它。您的应用程序中可能会发生许多类型的错误,您有多种方法可以处理这些错误,但在本节中,我将重点介绍一种类型的错误:异常。

Exceptions typically occur whenever you find an unexpected circumstance. A typical (and highly frustrating) exception you’ll no doubt have experienced before is NullReferenceException, which is thrown when you attempt to access a variable that hasn’t been initialized.[3] If an exception occurs in a middleware component, it propagates up the pipeline, as shown in figure 4.14. If the pipeline doesn’t handle the exception, the web server returns a 500 status code to the user.

每当发现意外情况时,通常会发生异常。您以前无疑会遇到一个典型的(且非常令人沮丧的)异常是 NullReferenceException,当您尝试访问尚未初始化的变量时,会引发该异常。3 如果中间件组件中发生异常,它会沿管道向上传播,如图 4.14 所示。如果管道未处理异常,则 Web 服务器将向用户返回 500 状态代码。

alt text

Figure 4.14 An exception in the endpoint middleware propagates through the pipeline. If the exception isn’t caught by middleware earlier in the pipeline, a 500 “Server error” status code is sent to the user’s browser.
图 4.14 端点中间件中的异常通过管道传播。如果管道中较早的中间件未捕获异常,则会向用户的浏览器发送 500 的“服务器错误”状态代码。

In some situations, an error won’t cause an exception. Instead, middleware might generate an error status code. One such case occurs when a requested path isn’t handled. In that situation, the pipeline returns a 404 error.
在某些情况下,错误不会导致异常。相反,中间件可能会生成错误状态代码。当请求的路径未得到处理时,就会出现一种情况。在这种情况下,管道将返回 404 错误。

For APIs, which typically are consumed by apps (as opposed to end users), that result probably is fine. But for apps that typically generate HTML, such as Razor Pages apps, returning a 404 typically results in a generic, unfriendly page being shown to the user, as you saw in figure 4.8. Although this behavior is correct, it doesn’t provide a great experience for users of these types of applications.

对于通常由应用程序 (而不是最终用户) 使用的 API,该结果可能很好。但对于通常生成 HTML 的应用程序(如 Razor Pages 应用程序),返回 404 通常会导致向用户显示通用的、不友好的页面,如图 4.8 所示。尽管此行为是正确的,但它不会为这些类型应用程序的用户提供出色的体验。

Error-handling middleware attempts to address these problems by modifying the response before the app returns it to the user. Typically, error-handling middleware returns either details on the error that occurred or a generic but friendly HTML page to the user. You’ll learn how to handle this use case in chapter 13 when you learn about generating responses with Razor Pages.

错误处理中间件尝试通过在应用程序将响应返回给用户之前修改响应来解决这些问题。通常,错误处理中间件会返回有关所发生错误的详细信息,或者向用户返回通用但友好的 HTML 页面。在第 13 章中,您将了解如何使用 Razor Pages 生成响应,从而了解如何处理此用例。

The remainder of this section looks at the two main types of exception-handling middleware that’s available for use in your application. Both are available as part of the base ASP.NET Core framework, so you don’t need to reference any additional NuGet packages to use them.

本节的其余部分将介绍可在应用程序中使用的两种主要类型的异常处理中间件。两者都作为基本 ASP.NET Core 框架的一部分提供,因此无需引用任何其他 NuGet 包即可使用它们。

4.3.1 Viewing exceptions in development: DeveloperExceptionPage

查看开发中的异常:DeveloperExceptionPage

When you’re developing an application, you typically want access to as much information as possible when an error occurs somewhere in your app. For that reason, Microsoft provides DeveloperExceptionPageMiddleware, which you can add to your middleware pipeline by using
在开发应用程序时,您通常希望在应用程序中的某个位置发生错误时访问尽可能多的信息。因此,Microsoft 提供了 DeveloperExceptionPageMiddleware,您可以使用

app.UseDeveloperExceptionPage();

Note As shown previously, WebApplication automatically adds this middleware to your middleware pipeline when you’re running in the Development environment, so you don’t need to add it explicitly. You’ll learn more about environments in chapter 10.
注意 如前所述,当您在 Development 环境中运行时,WebApplication 会自动将此中间件添加到您的中间件管道中,因此您无需显式添加它。您将在第 10 章中了解有关环境的更多信息。

When an exception is thrown and propagates up the pipeline to this middleware, it’s captured. Then the middleware generates a friendly HTML page, which it returns with a 500 status code, as shown in figure 4.15. This page contains a variety of details about the request and the exception, including the exception stack trace; the source code at the line the exception occurred; and details on the request, such as any cookies or headers that were sent.

当引发异常并沿管道向上传播到此中间件时,会捕获该异常。然后,中间件生成一个友好的 HTML 页面,该页面返回一个 500 状态代码,如图 4.15 所示。此页面包含有关请求和异常的各种详细信息,包括异常堆栈跟踪;发生异常的行处的源代码;以及有关请求的详细信息,例如发送的任何 Cookie 或标头。

alt text

Figure 4.15 The developer exception page shows details about the exception when it occurs during the process of a request. The location in the code that caused the exception, the source code line itself, and the stack trace are all shown by default. You can also click the Query, Cookies, Headers, and Routing buttons to reveal further details about the request that caused the exception.
图 4.15 开发者异常页面展示请求过程中发生的异常详情。默认情况下,将显示代码中导致异常的位置、源代码行本身和堆栈跟踪。您还可以单击 Query、Cookie、Headers 和 Routing 按钮,以显示有关导致异常的请求的更多详细信息。

Having these details available when an error occurs is invaluable for debugging a problem, but they also represent a security risk if used incorrectly. You should never return more details about your application to users than absolutely necessary, so you should use DeveloperExceptionPage only when developing your application. The clue is in the name!

在发生错误时提供这些详细信息对于调试问题非常有价值,但如果使用不当,它们也会带来安全风险。您绝不应向用户返回有关应用程序的更多详细信息,因此您只应在开发应用程序时使用 DeveloperExceptionPage。线索就在名字里!

Warning Never use the developer exception page when running in production. Doing so is a security risk, as it could publicly reveal details about your application’s code, making you an easy target for attackers. WebApplication uses the correct behavior by default and adds the middleware only when running in development.
警告 在生产环境中运行时,切勿使用开发人员异常页面。这样做会带来安全风险,因为它可能会公开泄露有关应用程序代码的详细信息,使您很容易成为攻击者的目标。默认情况下,WebApplication 使用正确的行为,并且仅在开发中运行时添加中间件。

If the developer exception page isn’t appropriate for production use, what should you use instead? Luckily, you can use another type of general-purpose error-handling middleware in production: ExceptionHandlerMiddleware.

如果开发人员例外页面不适合生产使用,您应该使用什么?幸运的是,您可以在生产中使用另一种类型的通用错误处理中间件:ExceptionHandlerMiddleware。

4.3.2 Handling exceptions in production: ExceptionHandlerMiddleware

4.3.2 在生产环境中处理异常:ExceptionHandlerMiddleware

The developer exception page is handy when you’re developing your applications, but you shouldn’t use it in production, as it can leak information about your app to potential attackers. You still want to catch errors, though; otherwise, users will see unfriendly error pages or blank pages, depending on the browser they’re using.
在开发应用程序时,开发人员异常页面很方便,但不应在生产环境中使用它,因为它可能会将有关应用程序的信息泄露给潜在的攻击者。不过,您仍然希望捕获错误;否则,用户将看到不友好的错误页面或空白页面,具体取决于他们使用的浏览器。

You can solve this problem by using ExceptionHandlerMiddleware. If an error occurs in your application, the user will see a custom error response that’s consistent with the rest of the application but provides only necessary details about the error. For a minimal API application, that response could be JSON or plain text, as shown in figure 4.16.

您可以使用 ExceptionHandlerMiddleware 来解决此问题。如果您的应用程序中发生错误,用户将看到一个自定义错误响应,该响应与应用程序的其余部分一致,但仅提供有关错误的必要详细信息。对于最小的 API 应用程序,该响应可以是 JSON 或纯文本,如图 4.16 所示。

alt text

Figure 4.16 Using the ExceptionHandlerMiddleware, you can return a generic error message when an exception occurs, ensuring that you don’t leak any sensitive details about your application in production.
图 4.16 使用 ExceptionHandlerMiddleware,您可以在发生异常时返回通用错误消息,从而确保在生产环境中不会泄露有关应用程序的任何敏感详细信息。

For Razor Pages apps, you can create a custom error response, such as the one shown in figure 4.17. You maintain the look and feel of the application by using the same header, displaying the currently logged-in user, and displaying an appropriate message to the user instead of full details on the exception.

对于 Razor Pages 应用程序,您可以创建自定义错误响应,如图 4.17 所示。通过使用相同的标头,显示当前登录的用户,并向用户显示适当的消息,而不是有关异常的完整详细信息,可以维护应用程序的外观。

alt text

Figure 4.17 A custom error page created by ExceptionHandlerMiddleware. The custom error page can have the same look and feel as the rest of the application by reusing elements such as the header and footer. More important, you can easily control the error details displayed to users.
图 4.17 由 ExceptionHandlerMiddleware 创建的自定义错误页面。通过重用 header 和 footer 等元素,自定义错误页面可以具有与应用程序其余部分相同的外观。更重要的是,您可以轻松控制向用户显示的错误详细信息。

Given the differing requirements for error handlers in development and production, most ASP.NET Core apps add their error-handler middleware conditionally, based on the hosting environment. WebApplication automatically adds the developer exception page when running in the development hosting environment, so you typically add ExceptionHandlerMiddleware when you’re not in the development environment, as shown in the following listing.

鉴于开发和生产中对错误处理程序的要求不同,大多数 ASP.NET Core 应用程序都会添加他们的 error-handler 中间件。WebApplication 在开发托管环境中运行时会自动添加开发人员异常页面,因此您通常在不在开发环境中时添加 ExceptionHandlerMiddleware,如下面的清单所示。

Listing 4.5 Adding exception-handler middleware when in production
列表 4.5 添加异常处理程序中间件在生产环境中

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
WebApplication app = builder.Build(); ❶
if (!app.Environment.IsDevelopment()) ❷
{
    app.UseExceptionHandler("/error"); ❸
}
// additional middleware configuration
app.MapGet("/error", () => "Sorry, an error occurred"); ❹

❶ In development, WebApplication automatically adds the developer exception page middleware.
在开发中,WebApplication 会自动添加开发者异常页面中间件。
❷ Configures a different pipeline when not running in development
在开发中未运行时配置不同的管道
❸ The ExceptionHandlerMiddleware won’t leak sensitive details when running in production.
ExceptionHandlerMiddleware 在生产环境中运行时不会泄露敏感细节。
❹ This error endpoint will be executed when an exception is handled.
处理异常时将执行此错误端点。

As well as demonstrating how to add ExceptionHandlerMiddleware to your middleware pipeline, this listing shows that it’s perfectly acceptable to configure different middleware pipelines depending on the environment when the application starts. You could also vary your pipeline based on other values, such as settings loaded from configuration.

除了演示如何将 ExceptionHandlerMiddleware 添加到中间件管道之外,此清单还表明,在应用程序启动时,根据环境配置不同的中间件管道是完全可以接受的。您还可以根据其他值(例如从配置加载的设置)来改变管道。

Note You’ll see how to use configuration values to customize the middleware pipeline in chapter 10.
注意 您将在第 10 章中看到如何使用配置值来自定义中间件管道。

When adding ExceptionHandlerMiddleware to your application, you typically provide a path to the custom error page that will be displayed to the user. In the example in listing 4.5, you used an error handling path of "/error":
在将 ExceptionHandlerMiddleware 添加到应用程序时,通常会提供将向用户显示的自定义错误页面的路径。在清单 4.5 的示例中,您使用了 “/error” 的错误处理路径:

app.UseExceptionHandler("/error");

ExceptionHandlerMiddleware invokes this path after it captures an exception to generate the final response. The ability to generate a response dynamically is a key feature of ExceptionHandlerMiddleware; it allows you to reexecute a middleware pipeline to generate the response sent to the user.

ExceptionHandlerMiddleware 在捕获异常后调用此路径以生成最终响应。动态生成响应的能力是 ExceptionHandlerMiddleware 的一个关键功能;它允许您重新执行中间件管道以生成发送给用户的响应。

Figure 4.18 shows what happens when ExceptionHandlerMiddleware handles an exception. It shows the flow of events when the minimal API endpoint for the "/" path generates an exception. The final response returns an error status code but also provides an error string, using the "/error" endpoint.
图 4.18 显示了当 ExceptionHandlerMiddleware 处理异常时会发生什么。它显示当 “/” 路径的最小 API 端点生成异常时的事件流。最终响应返回错误状态代码,但也使用“/error”端点提供错误字符串。

alt text

Figure 4.18 ExceptionHandlerMiddleware handling an exception to generate a JSON response. A request to the / path generates an exception, which is handled by the middleware. The pipeline is reexecuted, using the /error path to generate the JSON response.
图 4.18 ExceptionHandlerMiddleware 处理异常以生成 JSON 响应。对 / 路径的请求会生成异常,该异常由中间件处理。管道将重新执行,使用 /error 路径生成 JSON 响应。

The sequence of events when an unhandled exception occurs somewhere in the middleware pipeline (or in an endpoint) after ExceptionHandlerMiddleware is as follows:

当 ExceptionHandlerMiddleware 之后的中间件管道(或端点)中的某个位置发生未经处理的异常时,事件序列如下:

  1. A piece of middleware throws an exception.
    一个 middleware 会引发一个异常。

  2. ExceptionHandlerMiddleware catches the exception.
    ExceptionHandlerMiddleware 捕获异常。

  3. Any partial response that has been defined is cleared.
    将清除已定义的任何部分响应。

  4. The ExceptionHandlerMiddleware overwrites the request path with the provided error-handling path.
    ExceptionHandlerMiddleware 使用提供的错误处理路径覆盖请求路径。

  5. The middleware sends the request back down the pipeline, as though the original request had been for the error-handling path.
    中间件将请求发送回管道,就像原始请求是针对错误处理路径的一样。

  6. The middleware pipeline generates a new response as normal.
    中间件管道照常生成新的响应。

  7. When the response gets back to ExceptionHandlerMiddleware, it modifies the status code to a 500 error and continues to pass the response up the pipeline to the web server.
    当响应返回到 ExceptionHandlerMiddleware 时,它会将状态代码修改为 500 错误,并继续将响应从管道向上传递到 Web 服务器。

One of the main advantages of reexecuting the pipeline for Razor Page apps is the ability to have your error messages integrated into your normal site layout, as shown in figure 4.17. It’s certainly possible to return a fixed response when an error occurs without reexecuting the pipeline, but you wouldn’t be able to have a menu bar with dynamically generated links or display the current user’s name in the menu, for example. By reexecuting the pipeline, you ensure that all the dynamic areas of your application are integrated correctly, as though the page were a standard page of your site.

重新执行 Razor Page 应用程序的管道的主要优点之一是能够将错误消息集成到正常的站点布局中,如图所示4.17. 当错误发生时,当然可以在不重新执行管道的情况下返回固定的响应,但你将无法拥有一个带有 dynamic 的菜单栏生成的链接或在菜单中显示当前用户的名称。通过重新执行管道,您可以确保应用程序的所有动态区域都已正确集成,就像该页面是网站的标准页面一样。

Note You don’t need to do anything other than add ExceptionHandlerMiddleware to your application and configure a valid error-handling path to enable reexecuting the pipeline, as shown in figure 4.18. The middleware will catch the exception and reexecute the pipeline for you. Subsequent middleware will treat the reexecution as a new request, but previous middleware in the pipeline won’t be aware that anything unusual happened.
注意 除了将 ExceptionHandlerMiddleware 添加到您的应用程序并配置有效的错误处理路径以启用重新执行管道之外,您不需要执行任何其他作,如图 4.18 所示。中间件将捕获异常并为您重新执行管道。后续中间件会将重新执行视为新请求,但管道中先前的中间件不会意识到发生了任何异常情况。

Reexecuting the middleware pipeline is a great way to keep consistency in your web application for error pages, but you should be aware of some gotchas. First, middleware can modify a response generated farther down the pipeline only if the response hasn’t yet been sent to the client. This situation can be a problem if, for example, an error occurs while ASP.NET Core is sending a static file to a client. In that case, ASP.NET Core may start streaming bytes to the client immediately for performance reasons. In that case, the error-handling middleware won’t be able to run, as it can’t reset the response. Generally speaking, you can’t do much about this problem, but it’s something to be aware of.

重新执行中间件管道是保持 Web 应用程序错误页面一致性的好方法,但您应该注意一些问题。首先,仅当响应尚未发送到客户端时,中间件才能修改在管道中较远处生成的响应。例如,如果在 ASP.NET Core 向客户端发送静态文件时发生错误,则这种情况可能是一个问题。在这种情况下,出于性能原因,ASP.NET Core 可能会立即开始将字节流式传输到客户端。发生这种情况时,错误处理中间件将无法运行,因为它无法重置响应。一般来说,您对此问题无能为力,但需要注意。

A more common problem occurs when the error-handling path throws an error during the reexecution of the pipeline. Imagine that there’s a bug in the code that generates the menu at the top of the page in a Razor Pages app:
当错误处理路径在重新执行管道期间引发错误时,会出现更常见的问题。假设在 Razor Pages 应用程序中生成页面顶部菜单的代码中存在一个 bug:

  1. When the user reaches your home page, the code for generating the menu bar throws an exception.
    当用户访问您的主页时,用于生成菜单栏的代码会引发异常。

  2. The exception propagates up the middleware pipeline.
    异常沿中间件管道向上传播。

  3. When reached, ExceptionHandlerMiddleware captures it, and the pipe is reexecuted, using the error-handling path.
    到达时,ExceptionHandlerMiddleware 会捕获它,并使用错误处理路径重新执行管道。

  4. When the error page executes, it attempts to generate the menu bar for your app, which again throws an exception.
    当错误页面执行时,它会尝试为您的应用程序生成菜单栏,这再次引发异常。

  5. The exception propagates up the middleware pipeline.
    异常沿中间件管道向上传播。

  6. ExceptionHandlerMiddleware has already tried to intercept a request, so it lets the error propagate all the way to the top of the middleware pipeline.
    ExceptionHandlerMiddleware 已经尝试拦截一个请求,所以它允许错误一直传播到中间件管道的顶部。

  7. The web server returns a raw 500 error, as though there were no error-handling middleware at all.
    Web 服务器返回原始 500 错误,就好像根本没有错误处理中间件一样。

Thanks to this problem, it’s often good practice to make your error-handling pages as simple as possible to reduce the possibility that errors will occur.
由于这个问题,通常最好让你的错误处理页面尽可能简单,以减少发生错误的可能性。

Warning If your error-handling path generates an error, the user will see a generic browser error. It’s often better to use a static error page that always works than a dynamic page that risks throwing more errors. You can see an alternative approach using a custom error handling function in this post: http://mng.bz/0Kmx.
警告 如果错误处理路径生成错误,则用户将看到通用浏览器错误。使用始终有效的静态错误页面通常比使用动态页面。您可以在这篇文章中看到使用自定义错误处理函数的替代方法:http://mng.bz/0Kmx

Another consideration when building minimal API applications is that you generally don’t want to return HTML. Returning an HTML page to an application that’s expecting JSON could easily break it. Instead, the HTTP 500 status code and a JSON body describing the error are more useful to a consuming application. Luckily, ASP.NET Core allows you to do exactly this when you create minimal APIs and web API controllers.

构建最小 API 应用程序时的另一个注意事项是,您通常不希望返回 HTML。 将 HTML 页面返回到需要 JSON 的应用程序很容易破坏它。相反,HTTP 500 状态代码和描述错误的 JSON 正文对于使用应用程序更有用。幸运的是,ASP.NET Core 允许您在创建最小 API 和 Web API 控制器时执行此作。

Note I discuss how to add this functionality with minimal APIs in chapter 5 and with web APIs in chapter 20.
注意:我在第 5 章中讨论了如何使用最少的 API 添加此功能,在第 20 章中讨论了如何使用 Web API 添加此功能。

That brings us to the end of middleware in ASP.NET Core for now. You’ve seen how to use and compose middleware to form a pipeline, as well as how to handle exceptions in your application. This information will get you a long way when you start building your first ASP.NET Core applications. Later, you’ll learn how to build your own custom middleware, as well as how to perform complex operations on the middleware pipeline, such as forking it in response to specific requests. In chapter 5, you’ll look in depth at minimal APIs and at how they can be used to build JSON APIs.

现在,我们来到了 ASP.NET Core 中中间件的结尾。您已经了解了如何使用和组合中间件来形成管道,以及如何处理应用程序中的异常。当您开始构建您的第一个 ASP.NET Core 应用程序时,此信息将对您有所帮助。稍后,您将学习如何构建自己的自定义中间件,以及如何在中间件管道上执行复杂的作,例如为响应特定请求而分叉。在第 5 章中,您将深入了解最小的 API 以及如何使用它们来构建 JSON API。

4.4 Summary

4.4 总结

  • Middleware has a similar role to HTTP modules and handlers in ASP.NET but is easier to reason about.
    中间件的作用与 ASP.NET 中的 HTTP 模块和处理程序类似,但更容易推理。
  • Middleware is composed in a pipeline, with the output of one middleware passing to the input of the next.
    中间件由管道组成,一个中间件的输出传递到下一个中间件的输入。
  • The middleware pipeline is two-way: requests pass through each middleware on the way in, and responses pass back through in reverse order on the way out.
    中间件管道是双向的:请求在传入时通过每个中间件,响应在传出时以相反的顺序传回。
  • Middleware can short-circuit the pipeline by handling a request and returning a response, or it can pass the request on to the next middleware in the pipeline.
    中间件可以通过处理请求并返回响应来使管道短路,也可以将请求传递给管道中的下一个中间件。
  • Middleware can modify a request by adding data to or changing the HttpContext object.
    中间件可以通过向 HttpContext 对象添加数据或更改 HttpContext 对象来修改请求。
  • If an earlier middleware short-circuits the pipeline, not all middleware will execute for all requests.
    如果较早的中间件使管道短路,则并非所有中间件都会针对所有请求执行。
  • If a request isn’t handled, the middleware pipeline returns a 404 status code.
    如果未处理请求,中间件管道将返回 404 状态代码。
  • The order in which middleware is added to WebApplication defines the order in which middleware will execute in the pipeline.
    中间件添加到 WebApplication 的顺序定义了中间件在管道中的执行顺序。
  • The middleware pipeline can be reexecuted as long as a response’s headers haven’t been sent.
    只要尚未发送响应的标头,就可以重新执行中间件管道。
  • When it’s added to a middleware pipeline, StaticFileMiddleware serves any requested files found in the wwwroot folder of your application.
    当它被添加到中间件管道时,StaticFileMiddleware 会提供在应用程序的 wwwroot 文件夹中找到的任何请求文件。
  • DeveloperExceptionPageMiddleware provides a lot of information about errors during development, but it should never be used in production.
    DeveloperExceptionPageMiddleware 在开发过程中提供了大量有关错误的信息,但绝不应该在生产中使用。
  • ExceptionHandlerMiddleware lets you provide user-friendly custom error-handling messages when an exception occurs in the pipeline. It’s safe for use in production, as it doesn’t expose sensitive details about your application.
    ExceptionHandlerMiddleware 允许您在管道中发生异常时提供用户友好的自定义错误处理消息。它可以安全地用于生产环境,因为它不会暴露有关应用程序的敏感详细信息。
  • Microsoft provides some common middleware, and many third-party options are available on NuGet and GitHub.
    Microsoft 提供了一些常见的中间件,NuGet 和 GitHub 上提供了许多第三方选项。

[1] Technically, middleware needs to be a function, as you’ll see in chapter 31, but it’s common to implement middleware as a C# class with a single method.
从技术上讲,中间件需要是一个函数,如第 31 章所示,但通常使用单个方法将中间件实现为 C# 类。

[2] The downside to this approach is that it can hide exactly which middleware is being added to the pipeline. When the answer isn’t clear, I typically search for the source code of the extension method directly in GitHub (https://github.com/aspnet/aspnetcore).
这种方法的缺点是它可以准确隐藏要添加到管道的中间件。当答案不明确时,我通常会直接在 GitHub (https://github.com/aspnet/aspnetcore) 中搜索扩展方法的源代码。

[3] C# 8.0 introduced non-nullable reference types, which provide a way to handle null values more clearly, with the promise of finally ridding .NET of NullReferenceExceptions! The ASP.NET Core framework libraries in .NET 7 have fully embraced nullable reference types. See the documentation to learn more: http://mng.bz/7V0g.
C# 8.0 引入了不可为 null 的引用类型,它提供了一种更清晰地处理 null 值的方法,并有望最终消除 .NET 的 NullReferenceExceptions!.NET 7 中的 ASP.NET Core 框架库已完全采用可为 null 的引用类型。请参阅文档以了解更多信息:http:// mng.bz/7V0g。

Leave a Reply

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