ASP.NET Core in Action 30 Building ASP.NET Core apps with the generic host and Startup

Part 5 Going further with ASP.NET Core‌

第 5 部分:进一步了解 ASP.NET Core

Parts 1 through 4 of this book touched on all the aspects of ASP.NET Core you need to learn to build an HTTP application, whether that’s server-rendered applications using Razor Pages or JavaScript Object Notation (JSON) APIs using minimal APIs. In part 5 we look at four topics that build on what you’ve learned so far: customizing ASP.NET Core to your needs, interacting with third-party HTTP APIs, background services, and testing.
本书的第 1 部分到第 4 部分介绍了构建 HTTP 应用程序需要学习的 ASP.NET Core 的所有方面,无论是使用 Razor Pages 的服务器呈现的应用程序,还是使用最少 API 的 JavaScript 对象表示法 (JSON) API。在第 5 部分中,我们将介绍基于您目前所学知识的四个主题:根据您的需求自定义 ASP.NET Core、与第三方 HTTP API 交互、后台服务和测试。

In chapter 30 we start by looking at an alternative way to bootstrap your ASP.NET Core applications, using the generic host instead of the WebApplication approach you’ve seen so far in the book. The generic host was the standard way to bootstrap apps before .NET 6 (and is the approach you’ll find in previous editions of this book), so it’s useful to recognize the pattern, but it also comes in handy for building non-HTTP applications, as you’ll see in chapter 34.
在第 30 章中,我们首先研究了一种替代方法来引导 ASP.NET Core 应用程序,使用通用主机而不是您在本书中到目前为止看到的 WebApplication 方法。在 .NET 6 之前,泛型主机是引导应用程序的标准方法(您将在本书的前几个版本中找到该方法),因此识别模式很有用,但它在构建非 HTTP 应用程序时也很方便,如第 34 章所示。

In part 1 you learned about the middleware pipeline, and you saw how it is fundamental to all ASP.NET Core applications. In chapter 31 you’ll learn how to take full advantage of the pipeline, creating branching middleware pipelines, custom middleware, and simple middleware- based endpoints. You’ll also learn how to handle some complex chicken-and-egg configuration issues that often arise in real-life applications. Finally, you’ll learn how to replace the built-in dependency injection container with a third-party alternative.
在第 1 部分中,您了解了中间件管道,并了解了它如何成为所有 ASP.NET Core 应用程序的基础。在第 31 章中,您将学习如何充分利用管道,创建分支中间件管道、自定义中间件和基于中间件的简单端点。您还将学习如何处理实际应用程序中经常出现的一些复杂的先有鸡还是先有蛋的配置问题。最后,您将学习如何将内置的依赖项注入容器替换为第三方替代方案。

In chapter 32 you’ll learn how to create custom components for working with Razor Pages and API controllers. You’ll learn how to create custom Tag Helpers and validation attributes, and I’ll introduce a new component—view components—for encapsulating logic with Razor view rendering. You’ll also learn how to replace the attribute-based validation framework used by default in ASP.NET Core with an alternative.
在第 32 章中,您将学习如何创建自定义组件以使用 Razor Pages 和 API 控制器。您将学习如何创建自定义标记帮助程序和验证属性,并且我将介绍一个新组件 — 视图组件 — 用于使用 Razor 视图渲染封装逻辑。您还将了解如何将 ASP.NET Core 中默认使用的基于属性的验证框架替换为替代框架。

Most apps you build aren’t designed to stand on their own. It’s common for your app to need to interact with APIs, whether those are APIs for sending emails, taking payments, or interacting with your own internal applications. In chapter 33 you’ll learn how to call these APIs using the IHttpClientFactory abstraction to simplify configuration, add transient fault handling, and avoid common pitfalls.
您构建的大多数应用程序都不是为了独立而构建的。您的应用通常需要与 API 交互,无论这些 API 是用于发送电子邮件、收款还是与您自己的内部应用程序交互的 API。在第 33 章中,您将学习如何使用 IHttpClientFactory 抽象调用这些 API,以简化配置、添加瞬态故障处理并避免常见陷阱。

This book deals primarily with serving HTTP traffic, both server-rendered web pages using Razor Pages and web APIs commonly used by mobile and single-page applications.
本书主要介绍提供 HTTP 流量,包括使用 Razor Pages 的服务器呈现的网页,以及移动和单页应用程序常用的 Web API。

However, many apps require long-running background tasks that execute jobs on a schedule or that process items from a queue. In chapter 34 I’ll show how you can create these long-running background tasks in your ASP.NET Core applications. I’ll also show how to create standalone services that have only background tasks, without any HTTP handling, and how to install them as a Windows Service or as a Linux systemd daemon.
但是,许多应用程序需要长时间运行的后台任务,这些任务按计划执行作业或处理队列中的项目。在第 34 章中,我将展示如何在 ASP.NET Core 应用程序中创建这些长时间运行的后台任务。我还将展示如何创建仅包含后台任务而没有任何 HTTP 处理的独立服务,以及如何将它们安装为 Windows 服务或 Linux systemd 守护程序。

Chapters 35 and 36, the final chapters, cover testing your application. The exact role of testing in application development can lead to philosophical arguments, but in these chapters I stick to the practicalities of testing your app with the xUnit test framework. You’ll see how to create unit tests for your apps, test code that’s dependent on EF Core using an in-memory database provider, and write integration tests that can test multiple aspects of your application at the same time.
第 35 章和第 36 章是最后几章,涵盖了测试您的应用程序。测试在应用程序开发中的确切作用可能会导致哲学争论,但在这些章节中,我将重点介绍使用 xUnit 测试框架测试应用程序的实用性。你将了解如何为应用创建单元测试,使用内存中数据库提供程序测试依赖于 EF Core 的代码,以及编写可以同时测试应用程序多个方面的集成测试。

In the fast-paced world of web development there’s always more to learn, but by the end of part 5 you should have everything you need to build applications with ASP.NET Core, whether they be server-rendered page-based applications, APIs, or background services.
在快节奏的 Web 开发世界中,总是有更多的东西需要学习,但在第 5 部分结束时,您应该拥有使用 ASP.NET Core 构建应用程序所需的一切,无论它们是服务器渲染的基于页面的应用程序、API 还是后台服务。

In the appendices for this book, I provide some background and resources about .NET. Appendix A describes how to prepare your development environment by installing .NET 7 and an IDE or editor. In appendix B you’ll find a list of resources I use to learn more about ASP.NET Core and to stay up to date with the latest features.
在本书的附录中,我提供了一些有关 .NET 的背景和资源。附录 A 介绍了如何通过安装 .NET 7 和 IDE 或编辑器来准备开发环境。在附录 B 中,您将找到我用来了解有关 ASP.NET Core 的更多信息并了解最新功能的资源列表。

30 Building ASP.NET Core apps with the generic host and Startup

30 使用通用主机构建 ASP.NET Core 应用程序 和 Startup

This chapter covers
本章涵盖

• Using the generic host and a Startup class to bootstrap your ASP.NET Core app
使用泛型主机和 Startup 类引导 ASP.NET Core 应用程序

• Understanding how the generic host differs from WebApplication
了解通用主机与 WebApplication 的区别

• Building a custom generic IHostBuilder
构建自定义通用 IHostBuilder

• Choosing between the generic host and minimal hosting
在通用主机和最小主机之间进行选择

Some of the biggest changes introduced in ASP.NET Core in .NET 6 were the minimal hosting APIs, namely the WebApplication and WebApplicationBuilder types you’ve seen throughout this book. These were introduced to dramatically reduce the amount of code needed to get started with ASP.NET Core and are now the default way to build ASP.NET Core apps.‌
在 .NET 6 中引入 ASP.NET Core 的一些最大变化是最小的托管 API,即您在本书中看到的 WebApplication 和 WebApplicationBuilder 类型。引入这些应用程序是为了显著减少开始使用 ASP.NET Core 所需的代码量,现在是构建 ASP.NET Core 应用程序的默认方式。

Before .NET 6, ASP.NET Core used a different approach to bootstrap your app: the generic host, IHost, IHostBuilder, and a Startup class. Even though this approach is not the default in .NET 7, it’s still valid, so it’s important that you’re aware of it, even if you don’t need to use it yourself. In this chapter I introduce the generic host and show how it relates to the minimal hosting APIs you’re already familiar with. In chapter 34 you’ll learn how to use the generic host approach to build nonweb apps too.
在 .NET 6 之前,ASP.NET Core 使用不同的方法来启动应用程序:泛型主机、IHost、IHostBuilder 和 Startup 类。即使此方法不是 .NET 7 中的默认方法,它仍然有效,因此即使您自己不需要使用它,了解它也很重要。在本章中,我将介绍通用主机,并展示它与您已经熟悉的最小托管 API 的关系。在第 34 章中,你也将学习如何使用通用的 host 方法来构建非 Web 应用程序。

I start by introducing the two main concepts: the generic host components (IHostBuilder and IHost) and the Startup class. These split your app bootstrapping code between two files, Program.cs and Startup.cs, handling different aspects of your app’s configuration. You’ll learn why this split was introduced, where each component is configured, and how it compares with minimal hosting using WebApplication.
首先,我将介绍两个主要概念:通用主机组件(IHostBuilder 和 IHost)和 Startup 类。这些选项将你的应用程序引导代码拆分为两个文件(Program.cs 和 Startup.cs),处理应用程序配置的不同方面。您将了解引入此拆分的原因、每个组件的配置位置,以及它与使用 WebApplication 的最小托管的比较。

In section 30.4 you’ll learn how the helper function Host.CreateDefaultBuilder() works and use this knowledge to customize the IHostBuilder instance. This can give you greater control than minimal hosting, which may be useful in some situations.
在第 30.4 节中,您将了解帮助程序函数 Host.CreateDefaultBuilder() 的工作原理,并利用这些知识自定义 IHostBuilder 实例。这可以为您提供比最小托管更大的控制权,这在某些情况下可能很有用。

In section 30.5 we take a step back and look at some of the drawbacks in the generic host bootstrapping code we’ve explored, particularly its apparent complexity compared to minimal hosting with WebApplication.
在 Section 30.5 中,我们退后一步,看看我们探索过的通用主机引导代码中的一些缺点,特别是与使用 WebApplication 进行最小托管相比,它明显的复杂性。

Finally, in section 30.6 I discuss some of the reasons you might nevertheless choose to use the generic host instead of minimal hosting in your .NET 7 app. In most cases I suggest using minimal hosting with WebApplication, but there are valid cases in which the generic host makes sense.
最后,在第 30.6 节中,我将讨论一些原因,您可能仍然选择在 .NET 7 应用程序中使用通用主机而不是最小托管。在大多数情况下,我建议对 WebApplication 使用最小托管,但在某些情况下,通用主机是有意义的。

30.1 Separating concerns between two files‌

30.1 在两个文件之间分离关注点

As you’ve seen throughout this book, the standard way to create an ASP.NET Core application in .NET 7 is with the WebApplicationBuilder and WebApplication classes inside Program.cs, using top-level statements. Before .NET 6, however, ASP.NET Core used a different approach, which you can still use in .NET 7 if you wish.‌‌
正如您在本书中所看到的,在 .NET 7 中创建 ASP.NET Core 应用程序的标准方法是使用顶级语句在 Program.cs 中使用 WebApplicationBuilder 和 WebApplication 类。但是,在 .NET 6 之前,ASP.NET Core 使用不同的方法,如果您愿意,您仍然可以在 .NET 7 中使用该方法。

This approach typically uses a traditional static void Main() entry point (although top-level statements are supported) and splits its bootstrapping code across two files, as shown in figure 30.1:
这种方法通常使用传统的静态 void Main() 入口点(尽管支持顶级语句),并将其引导代码拆分为两个文件,如图 30.1 所示:

• Program.cs—This contains the entry point for the application, which bootstraps a host object. This is where you configure the infrastructure of your application, such as Kestrel, integration with Internet Information Services (IIS), and configuration sources.
Program.cs - 包含应用程序的入口点,用于引导主机对象。您可以在此处配置应用程序的基础结构,例如 Kestrel、与 Internet Information Services (IIS) 的集成以及配置源。

• Startup.cs—The Startup class is where you configure your dependency injection (DI) container, your middleware pipeline, and your application’s endpoints.
Startup.cs - Startup 类用于配置依赖关系注入 (DI) 容器、中间件管道和应用程序的端点。

alt text

Figure 30.1 The different responsibilities of the Program and Startup classes in an ASP.NET Core app that uses the generic host instead of WebApplication
图 30.1 使用泛型主机而不是 WebApplication 的 ASP.NET Core 应用程序中 Program 和 Startup 类的不同职责

We’ll look at each of these files in turn in section 30.2 and 30.3 to see how they might look for a typical Razor Pages app. I discuss the generic host at the center of this setup and compare the approach with the newer WebApplication APIs you’ve used so far throughout the book.
我们将在第 30.2 节和第 30.3 节中依次查看这些文件,以了解它们在典型 Razor Pages 应用程序中的外观。我将讨论此设置中心的通用主机,并将该方法与您在本书中到目前为止使用的较新的 WebApplication API 进行比较。

30.2 The Program class: Building a Web Host‌

30.2 Program 类:构建 Web 主机

All ASP.NET Core apps are fundamentally console applications. With the Startup-based hosting model, the Main entry point builds and runs an IHost instance, as shown in the following listing, which shows a typical Program.cs file. The IHost is the core of your ASP.NET Core application: it contains the HTTP server (Kestrel) for handling requests, along with all the necessary services and configuration to generate responses.‌‌
所有 ASP.NET Core 应用程序基本上都是控制台应用程序。使用基于启动的托管模型,Main 入口点构建并运行 IHost 实例,如下面的清单所示,其中显示了一个典型的 Program.cs 文件。IHost 是 ASP.NET Core 应用程序的核心:它包含用于处理请求的 HTTP 服务器 (Kestrel),以及用于生成响应的所有必要服务和配置。

Listing 30.1 The Program.cs file configures and runs an IHost
清单 30.1 Program.cs 文件配置并运行 IHost

public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args) ❶
.Build() ❷
.Run(); ❸
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args) ❹
.ConfigureWebHostDefaults(webBuilder => ❺
{
webBuilder.UseStartup<Startup>(); ❻
});
}

❶ Creates an IHostBuilder using the CreateHostBuilder method
使用 CreateHostBuilder 方法创建 IHostBuilder

❷ Builds and returns an instance of IHost from the IHostBuilder
从 IHostBuilder构建并返回 IHost 的实例

❸ Runs the IHost and starts listening for requests and generating responses
运行 IHost 并开始侦听请求并生成响应

❹ Creates an IHostBuilder using the default configuration
使用默认配置创建 IHostBuilder

❺ Configures the application to use Kestrel and listen to HTTP requests
将应用程序配置为使用 Kestrel 并侦听 HTTP 请求

❻ The Startup class defines most of your application’s configuration.
Startup 类定义了应用程序的大部分配置。

The Main function contains all the basic initialization code required to create a web server and to start listening for requests. It uses an IHostBuilder, created by the call to CreateDefaultBuilder, to define how the generic IHost is configured, before instantiating the IHost with a call to Build().
Main 函数包含创建 Web 服务器和开始侦听请求所需的所有基本初始化代码。它使用通过调用 CreateDefaultBuilder 创建的 IHostBuilder 来定义泛型 IHost 的配置方式,然后再通过调用 Build() 实例化 IHost。

TIP The IHost object represents your built application. The WebApplication type you’ve used throughout the book also implements IHost.
提示:IHost 对象表示您构建的应用程序。您在本书中介绍的 WebApplication 类型也实现了 IHost。

Much of your app’s configuration takes place in the IHostBuilder created by the call to CreateDefaultBuilder, but it delegates some responsibility to a separate class, Startup. The Startup class referenced in the generic UseStartup<> method is where you configure your app’s services and define your middleware pipeline.
应用程序的大部分配置都发生在由调用 CreateDefaultBuilder 创建的 IHostBuilder 中,但它将一些责任委托给单独的类 Startup。泛型 UseStartup<> 方法中引用的 Startup 类是您配置应用程序服务和定义中间件管道的位置。

NOTE The code to build the IHostBuilder is extracted to a helper method called CreateHostBuilder. The name of this method is historically important, as it was used implicitly by tooling such as the Entity Framework Core (EF Core) tools, as I discuss in section 30.5.‌
注意:用于构建 IHostBuilder 的代码被提取到名为 CreateHostBuilder 的帮助程序方法中。此方法的名称在历史上很重要,因为它由 Entity Framework Core (EF Core) 工具等工具隐式使用,如我在第 30.5 节中讨论的那样。

You may be wondering why you need two classes for configuration: Program and Startup. Why not include all your app’s configuration in one class or the other? The idea is to separate code that changes often from code that rarely changes.
您可能想知道为什么需要两个类进行配置:Program 和 Startup。为什么不将应用程序的所有配置都包含在一个类或另一个类中呢?这个想法是将经常更改的代码与很少更改的代码分开。

The Program class for two different ASP.NET Core applications typically look similar, but the Startup classes often differ significantly (though they all follow the same basic pattern, as you’ll see in section 30.3). You’ll rarely find that you need to modify Program as your application grows, whereas you’ll normally update Startup whenever you add additional features. For example, if you add a new NuGet dependency to your project, you’ll normally need to update Startup to make use of it.
两个不同的 ASP.NET Core 应用程序的 Program 类通常看起来相似,但 Startup 类通常有很大不同(尽管它们都遵循相同的基本模式,如您将在第 30.3 节中看到的那样)。您很少会发现需要随着应用程序的增长而修改 Program,而您通常会在添加其他功能时更新 Startup。例如,如果向项目添加新的 NuGet 依赖项,则通常需要更新 Startup 才能使用它。

The Program class is where a lot of app configuration takes place, but this is mostly hidden inside the Host.CreateDefaultBuilder method.
Program 类是进行大量应用程序配置的地方,但这主要隐藏在 Host.CreateDefaultBuilder 方法中。

CreateDefaultBuilder is a static helper method that simplifies the bootstrapping of your app by creating an IHostBuilder with some common configuration. This is similar to the way you’ve used WebApplication.CreateDefaultBuilder() throughout the book.
CreateDefaultBuilder 是一种静态帮助程序方法,它通过创建具有一些常见配置的 IHostBuilder 来简化应用程序的启动。这类似于您在整本书中使用 WebApplication.CreateDefaultBuilder() 的方式。

NOTE You can create custom HostBuilder instances if you want to customize the default setup and create a completely custom IHost instance, as you’ll see in section 30.4. This is different from WebApplicationBuilder, which always uses the same defaults.
注意:如果您想自定义默认设置并创建完全自定义的 IHost 实例,则可以创建自定义 HostBuilder 实例,如第 30.4 节所示。这与 WebApplicationBuilder 不同,后者始终使用相同的默认值。

The other helper method used by default is ConfigureWebHostDefaults. This uses a WebHostBuilder object to configure Kestrel to listen for HTTP requests.‌
默认情况下使用的另一个帮助程序方法是 ConfigureWebHostDefaults。这使用 WebHostBuilder 对象将 Kestrel 配置为侦听 HTTP 请求。

Creating services with the generic host
使用通用主机创建服务

It might seem strange that you must call ConfigureWebHostDefaults as well as CreateDefaultBuilder. Couldn’t we have one method? Isn’t handling HTTP requests the whole point of ASP.NET Core?
您必须调用 ConfigureWebHostDefaults 和 CreateDefaultBuilder 似乎很奇怪。我们不能有一种方法吗?处理 HTTP 请求不是 ASP.NET Core 的全部意义所在吗?

Well, yes and no! ASP.NET Core 3.0 introduced the concept of a generic host. This allows you to use much of the same framework as ASP.NET Core applications to write non-HTTP applications. These apps can run as console apps or can be installed as Windows services (or as systemd daemons in Linux) to run background tasks or read from message queues, for example.
嗯,是的,也不是!ASP.NET Core 3.0 引入了通用主机的概念。这允许您使用与 ASP.NET Core 应用程序相同的框架来编写非 HTTP 应用程序。例如,这些应用程序可以作为控制台应用程序运行,也可以作为 Windows 服务(或 Linux 中的 systemd 守护程序)安装,以运行后台任务或从消息队列中读取数据。

Kestrel and the web framework of ASP.NET Core build on top of the generic host functionality introduced in ASP.NET Core 3.0. To configure a typical ASP.NET Core app, you configure the generic host features that are common across all apps—features such as configuration, logging, and dependency services. For web applications, you then also configure the services, such as Kestrel, that are necessary to handle web requests. In chapter 34 you’ll see how to build applications using the generic host to run scheduled tasks and build background services.
Kestrel 和 ASP.NET Core 的 Web 框架构建在 ASP.NET Core 3.0 中引入的通用主机功能之上。要配置典型的 ASP.NET Core 应用程序,您需要配置所有应用程序中通用的通用主机功能,例如配置、日志记录和依赖项服务等功能。对于 Web 应用程序,您还可以配置处理 Web 请求所需的服务,例如 Kestrel。在第 34 章中,您将看到如何使用通用主机构建应用程序来运行计划任务和构建后台服务。

Even in .NET 7, WebApplication and WebApplicationBuilder use the generic host behind the scenes. You can read more about the evolution of ASP.NET Core’s bootstrapping code and the relationship between IHost and WebApplication on my blog at https://andrewlock.net/exploring-dotnet-6-part-2-comparing-webapplicationbuilder-to-the-generic-host/.
即使在 .NET 7 中,WebApplication 和 WebApplicationBuilder 也在后台使用通用主机。您可以在我的博客 https://andrewlock.net/exploring-dotnet-6-part-2-comparing-webapplicationbuilder-to-the-generic-host/ 上阅读有关 ASP.NET Core 引导代码的演变以及 IHost 和 WebApplication 之间的关系的更多信息。

Once the configuration of the IHostBuilder is complete, the call to Build produces the IHost instance, but the application still isn’t handling HTTP requests yet. It’s the call to Run() that starts the HTTP server listening. At this point, your application is fully operational and can respond to its first request from a remote browser.
IHostBuilder 的配置完成后,对 Build 的调用将生成 IHost 实例,但应用程序仍未处理 HTTP 请求。对 Run() 的调用将启动 HTTP 服务器侦听。此时,您的应用程序已完全运行,并且可以响应来自远程浏览器的第一个请求。

30.3 The Startup class: Configuring your application‌

30.3 Startup 类:配置应用程序

As you’ve seen, Program is responsible for configuring a lot of the infrastructure for your app, but you configure some of your app’s behavior in Startup. The Startup class is responsible for configuring two main aspects of your application:
如你所见,Program 负责为应用程序配置大量基础结构,但你在 Startup 中配置应用程序的一些行为。Startup 类负责配置应用程序的两个主要方面:

• DI container service registration
DI 集装箱服务注册

• Middleware configuration and mapping of endpoints
中间件配置和端点映射

You configure each of these aspects in its own method in Startup: service registration in ConfigureServices and middleware/endpoint configuration in Configure. A typical outline of Startup is shown in the following listing.
您可以在 Startup 中在其自己的方法中配置每个方面:ConfigureServices 中的服务注册和 Configure 中的中间件/终端节点配置。下面的清单显示了 Startup 的典型轮廓。

Listing 30.2 An outline of Startup.cs showing how each aspect is configured
清单 30.2 Startup.cs概述,显示每个 aspect 是如何配置的

public class Startup
{
public void ConfigureServices(IServiceCollection services) ❶
{
// method details
}
public void Configure(IApplicationBuilder app) ❷
{
// method details
}
}

❶ Configures services by registering them with the IServiceCollection
通过在 IServiceCollection中注册服务来配置服务
❷ Configures the middleware pipeline for handling HTTP requests
配置用于处理 HTTP 请求的中间件管道

The IHostBuilder created in Program automatically calls ConfigureServices and then Configure, as shown in figure 30.2. Each call configures a different part of your application, making it available for subsequent method calls. Any services registered in the ConfigureServices method are available to the Configure method. Once configuration is complete, you create an IHost by calling Build() on the IHostBuilder.
在 Program 中创建的 IHostBuilder 会自动调用 ConfigureServices,然后调用 Configure,如图 30.2 所示。每次调用都会配置应用程序的不同部分,使其可用于后续方法调用。在 ConfigureServices 方法中注册的任何服务都可用于 Configure 方法。配置完成后,您可以通过在 IHostBuilder 上调用 Build() 来创建 IHost。

alt text

Figure 30.2 The IHostBuilder is created in Program.cs and calls methods on Startup to configure the application’s services and middleware pipeline. Once configuration is complete, the IHost is created by calling Build() on the IHostBuilder.
图 30.2 IHostBuilder 是在 Program.cs中创建的,并在启动时调用方法来配置应用程序的服务和中间件管道。配置完成后,通过在 IHostBuilder 上调用 Build() 来创建 IHost。

An interesting point about the Startup class is that it doesn’t implement an interface as such. Instead, the methods are invoked by using reflection to find methods with the predefined names of Configure and ConfigureServices. This makes the class more flexible and enables you to modify the signature of the Configure method to inject any services you registered in ConfigureServices using DI.
关于 Startup 类的一个有趣之处在于,它没有实现这样的接口。相反,通过使用反射来查找具有预定义名称 Configure 和 ConfigureServices 的方法,从而调用这些方法。这使得该类更加灵活,并使您能够修改 Configure 方法的签名,以注入您使用 DI 在 ConfigureServices 中注册的任何服务。

TIP If you’re not a fan of the flexible reflection approach, you can implement the IStartup interface or derive from the StartupBase class, which provide the method signatures shown previously in listing 30.2. If you take this approach, you won’t be able to use DI to inject services into the Configure() method.‌‌
提示:如果您不喜欢灵活的反射方法,则可以实现 IStartup 接口或从 StartupBase 类派生,这些类提供前面清单 30.2 中所示的方法签名。如果采用此方法,则无法使用 DI 将服务注入 Configure() 方法。

ConfigureServices is where you add all your required and custom services to the DI container, exactly as you do with WebApplicationBuilder.Services in a typical .NET 7 ASP.NET Core app. The following listing shows how you might configure all the services for the Razor Pages recipe app you’ve seen throughout this book. This listing also shows how you can access the IConfiguration for your app: by injecting into the Startup constructor. You’ll see how to customize your app’s configuration in section 30.4.
在 ConfigureServices 中,您可以将所有必需的自定义服务添加到 DI 容器中,就像在典型的 .NET 7 ASP.NET Core 应用程序中使用 WebApplicationBuilder.Services 一样。以下清单显示了如何为本书中介绍的 Razor Pages 配方应用程序配置所有服务。此清单还显示了如何访问应用程序的 IConfiguration:通过注入 Startup 构造函数。您将在 Section 30.4 中看到如何自定义应用程序的配置。

Listing 30.3 Registering services with DI in ConfigureServices
清单 30.3 在 ConfigureServices 中向 DI 注册服务

public class Startup
{
public IConfiguration Configuration { get; } ❶
public Startup(IConfiguration configuration) ❶
{
Configuration = configuration;
}
public void ConfigureServices(IServiceCollection services) ❷
{
var conn = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<AppDbContext>(options => ❸
options.UseSqlite(conn)); ❸
services.AddDefaultIdentity<ApplicationUser>(options => ❸
options.SignIn.RequireConfirmedAccount = true) ❸
.AddEntityFrameworkStores<AppDbContext>(); ❸
services.AddScoped<RecipeService>(); ❹
services.AddRazorPages(); ❺
services.AddScoped<IAuthorizationHandler, IsRecipeOwnerHandler>();
services.AddAuthorizationBuilder()
.AddPolicy("CanManageRecipe",
p => p.AddRequirements(new IsRecipeOwnerRequirement()));
}
public void Configure(IApplicationBuilder app) => { /* Not shown */ }
}

❶ The IConfiguration for the app is injected into the constructor.
应用程序的 IConfiguration 被注入到构造函数中。

❷ You must register your services against the provided IServiceCollection.
您必须针对提供的 IServiceCollection 注册您的服务。

❸ Registers all the EF Core and ASP.NET Core Identity services
注册所有 EF Core 和 ASP.NET Core Identity 服务

❹ Registers the custom service implementations
注册自定义服务实现

❺ Registers the framework services
注册框架服务

After configuring all your services, you need to set up your middleware pipeline and map your endpoints. The process is similar to configuring your middleware pipeline using WebApplication:
配置完所有服务后,您需要设置中间件管道并映射终端节点。该过程类似于使用 WebApplication 配置中间件管道:

• You add middleware to the pipeline by calling Use extension methods on an IApplicationBuilder instance.
通过在 IApplicationBuilder 实例上调用 Use
扩展方法,将中间件添加到管道中。

• The order in which you add the middleware to the pipeline is important and defines the final pipeline order.
将中间件添加到管道的顺序非常重要,它定义了最终的管道顺序。

• You can add middleware conditionally based on the environment.
您可以根据环境有条件地添加中间件。

However, there are some important differences between the WebApplication approach you’ve seen so far and the Startup approach:
但是,到目前为止,您看到的 WebApplication 方法与 Startup 方法之间存在一些重要差异:

• The IWebHostEnvironment for your app is exposed directly on WebApplication.Environment. To access this information inside Startup, you must inject it into the constructor or the Configure method using DI.
应用程序的 IWebHostEnvironment 直接在 WebApplication.Environment 上公开。要在 Startup 中访问此信息,您必须使用 DI 将其注入到构造函数或 Configure 方法中。

• As you saw in chapter 4, WebApplication automatically adds a lot of middleware to your pipeline, such as routing middleware, endpoint middleware, and the authentication middleware. You must add this middleware manually when using the Startup approach.
如第 4 章所示,WebApplication 会自动向管道中添加大量中间件,例如路由中间件、端点中间件和身份验证中间件。使用 Startup 方法时,必须手动添加此中间件。

• WebApplication implements both IApplicationBuilder and IEndpointRouteBuilder, so you can add endpoints directly to WebApplication, by calling MapGet() or MapRazorPages(), for example.When using the Startup approach, you must call UseEndpoints() and map all your endpoints in a lambda method instead.
WebApplication 同时实现 IApplicationBuilder 和 IEndpointRouteBuilder,因此您可以通过调用 MapGet() 或 MapRazorPages() 等方式将端点直接添加到 WebApplication。使用 Startup 方法时,您必须调用 UseEndpoints() 并改为在 lambda 方法中映射所有终端节点。

• The Configure method is not async, so it’s cumbersome to do async tasks. By contrast, when using WebApplication, you’re free to use async methods between any of your general bootstrapping code.
Configure 方法不是异步的,因此执行异步任务很麻烦。相比之下,在使用 WebApplication 时,您可以在任何常规引导代码之间自由使用异步方法。

Despite these caveats, in many cases your Startup.Configure method will look almost identical to the way you configure the pipeline on WebApplication. The following listing shows how the Configure() method for the Razor Pages recipe app might look.‌尽管有这些注意事项,但在许多情况下,您的 Startup.Configure 方法看起来与您在 WebApplication 上配置管道的方式几乎相同。以下清单显示了 Razor Pages 配方应用的 Configure() 方法的外观。

Listing 30.4 Startup.Configure() for a Razor Pages application
列表 30.4 Razor Pages 应用程序的 Startup.Configure()

public class Startup
{
public void Configure(
IApplicationBuilder app, ❶
IWebHostEnvironment env) ❷
{
if (env.IsDevelopment()) ❸
{
app.UseDeveloperExceptionPage(); ❹
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting(); ❺
app.UseAuthentication();
app.UseAuthorization(); ❻
app.UseEndpoints(endpoints => ❼
{
endpoints.MapRazorPages(); ❽
});
}
}

❶ IApplicationBuilder is used to build the middleware pipeline.
IApplicationBuilder 用于构建中间件管道。

❷ Other services can be accepted as parameters.
其他服务可以作为参数接受。

❸ Different behavior when in development or production
开发或生产时的行为不同

❹ WebApplication adds this automatically. You must explicitly add it when using Startup.
WebApplication 会自动添加此内容。您必须在使用Startup 时显式添加它。

❺ Similarly, you must explicitly call UseRouting.
同样,您必须显式调用 UseRouting。

❻ Must always be placed between the call to UseRouting and UseEndpoints
必须始终放置在对 UseRouting 和 UseEndpoints的调用之间

❼ Adds the endpoint middleware, which executes the endpoints
添加执行终结点的终结点中间件

❽ Maps the Razor Pages endpoints
映射 Razor Pages 终结点

In this example, the IWebHostEnvironment object is injected into the Configure() method using DI so that you can configure the middleware pipeline differently in development and production. In this case, we add the DeveloperExceptionPageMiddleware to the pipeline when we’re running in development.‌
在此示例中,使用 DI 将 IWebHostEnvironment 对象注入到 Configure() 方法中,以便您可以在开发和生产中以不同的方式配置中间件管道。在本例中,我们在开发中运行时将 DeveloperExceptionPageMiddleware 添加到管道中。

NOTE Remember that WebApplication adds this middleware automatically, but with Startup you must add it manually. The same goes for all the other automatically added middleware.
注意:请记住,WebApplication 会自动添加此中间件,但使用 Startup 时,您必须手动添加它。所有其他自动添加的 middleware 也是如此。

After adding all the middleware to the pipeline, you come to the UseEndpoints() call, which adds the EndpointMiddleware to the pipeline. When you use WebApplication, you rarely need to call this, as WebApplication automatically adds it at the end of the pipeline, but when you use Startup, you should add it at the end of your pipeline.
将所有中间件添加到管道后,您将转到 UseEndpoints() 调用,该调用将 EndpointMiddleware 添加到管道中。当您使用 WebApplication 时,您很少需要调用它,因为 WebApplication 会自动将其添加到管道的末尾,但是当您使用 Startup 时,您应该将其添加到管道的末尾。

Note as well that the call to UseEndpoints() is where you define all the endpoints in your application. Whether they’re Razor Pages, Model-View-Controller (MVC) controllers, or minimal APIs, you must register them in the UseEndpoints() lambda.
另请注意,对 UseEndpoints() 的调用是定义应用程序中的所有终结点的位置。无论它们是 Razor Pages、Model-View-Controller (MVC) 控制器还是最小 API,都必须在 UseEndpoints() lambda 中注册它们。

NOTE Endpoints must be registered inside the call to UseEndpoints() using the IEndpointRouteBuilder instance from the lambda method.
注意:必须使用 lambda 方法中的 IEndpointRouteBuilder 实例在对 UseEndpoints() 的调用中注册终端节点。

Other than the noted differences, moving your service, middleware, and endpoint configuration between a Startup-based approach and WebApplication should be relatively simple, which may lead you to wonder whether there’s any good reason to choose the Startup approach over WebApplication. As always, the answer is “It depends,” but one possible reason is so that you can customize your IHostBuilder.
除了上述差异之外,在基于 Startup 的方法和 WebApplication 之间移动服务、中间件和端点配置应该相对简单,这可能会让您怀疑是否有任何充分的理由选择 Startup 方法而不是 WebApplication。与往常一样,答案是“视情况而定”,但一个可能的原因是您可以自定义 IHostBuilder。

30.4 Creating a custom IHostBuilder‌

30.4 创建自定义 IHostBuilder

As you saw in section 30.2, the default way to work with a Startup class in ASP.NET Core is to use the Host.CreateDefaultBuilder() method. This opinionated helper method sets up many defaults for your app. It is analogous to the WebApplication‌.CreateBuilder() method in that way.
如您在第 30.2 节中所见,在 ASP.NET Core 中使用 Startup 类的默认方法是使用 Host.CreateDefaultBuilder() 方法。这个固执己见的 helper 方法为您的应用程序设置了许多默认值。它类似于 WebApplication 。CreateBuilder() 方法。

However, you don’t have to use the CreateDefaultBuilder method to create an IHostBuilder instance: you can directly create a HostBuilder instance and customize it from scratch if you prefer. Before you start doing that, though, it’s worth seeing some of the things the CreateDefaultBuilder method gives you and what they’re used for. You may then consider customizing the default HostBuilder instance instead of creating a completely bespoke instance.‌
但是,您不必使用 CreateDefaultBuilder 方法创建 IHostBuilder 实例:如果您愿意,可以直接创建 HostBuilder 实例并从头开始自定义它。不过,在开始执行此作之前,有必要了解 CreateDefaultBuilder 方法为您提供的一些功能以及它们的用途。然后,您可以考虑自定义默认的 HostBuilder 实例,而不是创建完全定制的实例。

NOTE You can use Host.CreateDefaultBuilder() in .NET 7 even if you’re not using ASP.NET Core by installing the Microsoft.Extensions.Hosting package. You’ll learn how to create non-HTTP applications using the generic host in chapter 34.
注意:即使您没有使用 ASP.NET Core,也可以通过安装 Microsoft.Extensions.Hosting 包在 .NET 7 中使用 Host.CreateDefaultBuilder()。您将在第 34 章中学习如何使用通用主机创建非 HTTP 应用程序。

The defaults chosen by CreateDefaultBuilder are ideal when you’re initially setting up an app, but as your application grows, you may find you need to break it apart and tinker with some of the internals. The following listing shows a rough overview of the CreateDefaultBuilder method, so you can see how the HostBuilder is constructed. It’s not exhaustive or complete, but it should give you an idea of the amount of work the CreateDefaultBuilder method does for you!
CreateDefaultBuilder 选择的默认值在您最初设置应用程序时是理想的,但随着应用程序的增长,您可能会发现需要将其分解并修改一些内部结构。下面的清单显示了 CreateDefaultBuilder 方法的粗略概述,因此你可以看到 HostBuilder 是如何构造的。它并不详尽或完整,但它应该让您了解 CreateDefaultBuilder 方法为您完成的工作量!

Listing 30.5 The Host.CreateDefaultBuilder method
清单 30.5 Host.CreateDefaultBuilder 方法

public static IHostBuilder CreateDefaultBuilder(string[] args)
{
var builder = new HostBuilder() ❶
.UseContentRoot(Directory.GetCurrentDirectory()) ❷
.ConfigureHostConfiguration(IConfigurationBuilder config => ❸
{ ❸
config.AddEnvironmentVariables("DOTNET_"); ❸
config.AddCommandLine(args); ❸
}) ❸
.ConfigureAppConfiguration((hostingContext, config) => ❹
{ ❹
IHostEnvironment env = hostingContext.HostingEnvironment; ❹
config ❹
.AddJsonFile("appsettings.json") ❹
.AddJsonFile($"appsettings.{env.EnvironmentName}.json"); ❹
if (env.IsDevelopment()) ❹
{ ❹
config.AddUserSecrets(); ❹
} ❹
config ❹
.AddEnvironmentVariables() ❹
.AddCommandLine(); ❹
}) ❹
.ConfigureLogging((hostingContext, logging) => ❺
{ ❺
logging.AddConfiguration( ❺
hostingContext.Configuration.GetSection("Logging")); ❺
logging.AddConsole(); ❺
logging.AddDebug(); ❺
logging.AddEventSourceLogger(); ❺
logging.AddEventLog(); ❺
}) ❺
.UseDefaultServiceProvider((context, options) => ❻
{ ❻
var isDevelopment = context.HostingEnvironment ❻
.IsDevelopment(); ❻
options.ValidateScopes = isDevelopment; ❻
options.ValidateOnBuild = isDevelopment; ❻
}); ❻
return builder; ❼
}

❶ Creates an instance of HostBuilder
创建 HostBuilder的实例

❷ The content root defines the directory where configuration files can be found.
内容根定义可以找到配置文件的目录。

❸ Configures hosting settings such as determining the hosting environment
配置托管设置,例如确定托管环境

❹ Configures application settings
配置应用程序设置

❺ Sets up the logging infrastructure
设置日志记录基础设施

❻ Configures the DI container, optionally enabling verification settings
配置 DI 容器,可选择启用验证设置

❼ Returns HostBuilder for further configuration by calling extra methods before calling Build()
通过在调用 Build() 之前调用额外的方法返回 HostBuilder 以进行进一步配置

The first method called on HostBuilder is UseContentRoot(). This tells the application in which directory it can find any configuration or Razor files it needs later. This is typically the folder in which the application is running, hence the call to GetCurrentDirectory.
在 HostBuilder 上调用的第一个方法是 UseContentRoot()。这会告知应用程序稍后可以在哪个目录中找到所需的任何配置或 Razor 文件。这通常是运行应用程序的文件夹,因此调用 GetCurrentDirectory。

TIP Remember that ContentRoot is not where you store static files that the browser can access directly. That’s the WebRoot, typically wwwroot.
提示:请记住,ContentRoot 不是存储浏览器可以直接访问的静态文件的位置。这就是 WebRoot,通常是 wwwroot。

The ConfigureHostingConfiguration() method is where your application determines which HostingEnvironment it’s currently running in. The framework looks for environment variables that start with "DOTNET_" (such as the DOTNETENVIRONMENT variable you learned about in chapter 10) and command-line arguments to determine whether it’s running in a development or production environment. This is used to populate the IWebHostEnvironment object that’s used throughout your app.‌
ConfigureHostingConfiguration() 方法是应用程序确定它当前在哪个 HostingEnvironment 中运行的位置。框架会查找以 “DOTNET
” 开头的环境变量(例如您在第 10 章中学到的 DOTNET_ENVIRONMENT 变量)和命令行参数,以确定它是在开发环境中运行还是在生产环境中运行。这用于填充整个应用程序中使用的 IWebHostEnvironment 对象。

The ConfigureAppConfiguration() method is where you configure the main IConfiguration object for your app, populating it from appsettings.json files, environment variables, and User Secrets, for example. The default builder populates the configuration using all the sources shown in listing 30.5, which is similar to the configuration WebApplicationBuilder uses.‌
ConfigureAppConfiguration() 方法是为应用程序配置主 IConfiguration 对象的地方,例如,从 appsettings.json 文件、环境变量和用户密钥中填充它。默认构建器使用清单 30.5 中所示的所有源填充配置,这类似于 WebApplicationBuilder 使用的配置。

TIP There are some important differences in how the IConfiguration object is built using the default builder and the approach used by WebApplicationBuilder. You can read about these differences on my blog at http://mng.bz/e11V.
提示:使用默认生成器和 WebApplicationBuilder 使用的方法构建 IConfiguration 对象的方式存在一些重要差异。您可以在我的博客 http://mng.bz/e11V 上阅读这些差异。

Next up after app configuration comes ConfigureLogging(). ConfigureLogging is where you specify the logging settings and providers for your application, which you learned about in chapter 26. In addition to setting up the default ILoggerProviders, this method sets up log filtering, using the IConfiguration prepared in ConfigureAppConfiguration().
接下来,在应用程序配置之后是 ConfigureLogging()。ConfigureLogging 是指定应用程序的日志记录设置和提供程序的地方,您在第 26 章中了解了这一点。除了设置默认 ILoggerProviders 之外,此方法还使用 ConfigureAppConfiguration() 中准备的 IConfiguration 设置日志筛选。

The last method call shown in listing 30.5, UseDefaultServiceProvider, configures your app to use the built-in DI container. It also sets the ValidateScopes and ValidateOnBuild options based on the current HostingEnvironment. This ensures that when running the application in the development environment, the container automatically checks for captured dependencies, which you learned about in chapter 9.‌‌
清单 30.5 中显示的最后一个方法调用 UseDefaultServiceProvider 将您的应用程序配置为使用内置的 DI 容器。它还根据当前 HostingEnvironment 设置 ValidateScopes 和 ValidateOnBuild 选项。这可确保在开发环境中运行应用程序时,容器会自动检查捕获的依赖项,您在第 9 章中学到了这一点。

As you can see, CreateDefaultBuilder does a lot for you. In many cases, these defaults are exactly what you need, but if they’re not, the default builder is optional. You could call new HostBuilder() and start customizing it from there, but you’d need to set up everything that CreateHostBuilder does: logging, hosting configuration, and service provider configuration, as well as your app configuration.
如您所见,CreateDefaultBuilder 为您做了很多事情。在许多情况下,这些默认值正是您所需要的,但如果它们不是,则默认构建器是可选的。您可以调用新的 HostBuilder() 并从那里开始自定义它,但您需要设置 CreateHostBuilder 执行的所有作:日志记录、托管配置和服务提供商配置,以及您的应用程序配置。

An alternative approach is to layer additional configuration on top of the existing defaults. In the following listing, I show how to add a Seq logging provider to the configured providers using ConfigureLogging(), as well as how to reconfigure the app configuration to load only from the appsettings.json provider by clearing the default providers.
另一种方法是在现有默认值之上对其他配置进行分层。在下面的清单中,我将展示如何使用 ConfigureLogging() 将 Seq 日志记录提供程序添加到配置的提供程序中,以及如何通过清除默认提供程序来重新配置应用程序配置以仅从 appsettings.json 提供程序加载。

Listing 30.6 Customizing the default HostBuilder
清单 30.6 自定义默认的 HostBuilder

public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logBuilder => logBuilder.AddSeq()) ❶
.ConfigureAppConfiguration((hostContext, config) => ❷
{
config.Sources.Clear(); ❸
config.AddJsonFile("appsettings.json"); ❹
}
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}

❶ Adds the Seq logging provider to the configuration
将 Seq 日志记录提供程序添加到配置中

❷ HostBuilder provides a hosting context and an instance of ConfigurationBuilder.
HostBuilder 提供托管上下文和 ConfigurationBuilder 实例。

❸ Clears the providers configured by default in CreateDefaultBuilder
清除 CreateDefaultBuilder中默认配置的提供程序

❹ Adds a JSON configuration provider, providing the filename of the configuration file
添加 JSON 配置提供程序,提供配置文件的文件名

A new HostBuilder is created in CreateDefaultBuilder() and executes all the configuration methods you saw in listing 30.5. Next, the HostBuilder invokes the extra ConfigureLogging() and ConfigureAppConfiguration() methods added in listing 30.6. You can call any of the other configuration methods on HostBuilder to further customize the instance before calling Build().‌
在 CreateDefaultBuilder() 中创建一个新的 HostBuilder,并执行您在清单 30.5 中看到的所有配置方法。接下来,HostBuilder 调用清单 30.6 中添加的额外 ConfigureLogging() 和 ConfigureAppConfiguration() 方法。在调用 Build() 之前,您可以在 HostBuilder 上调用任何其他配置方法以进一步自定义实例。

NOTE Each call to a Configure() method on HostBuilder adds an extra configuration function to the setup code; these calls don’t replace existing Configure () calls. The configuration methods are executed in the same order in which they’re added to the HostBuilder, so they execute after the CreateDefaultBuilder() configuration methods.
注意:对 HostBuilder 上的 Configure() 方法的每次调用都会向设置代码添加一个额外的配置函数;这些调用不会替换现有的 Configure () 调用。配置方法的执行顺序与添加到 HostBuilder 的顺序相同,因此它们在 CreateDefaultBuilder() 配置方法之后执行。

One of the criticisms of early ASP.NET Core apps was that they were quite complex to understand when you’re getting started, and after working your way through this chapter, you might well be able to see why! In the next section we compare the generic host and Startup approach with the newer minimal hosting WebApplication approach and discuss when you might want to use one over the other.‌
对早期 ASP.NET Core 应用程序的批评之一是,当您开始时,它们非常难以理解,在完成本章之后,您很可能能够明白为什么!在下一节中,我们将通用 host 和 Startup 方法与较新的最小托管 WebApplication 方法进行比较,并讨论何时可能需要使用其中一种方法。

30.5 Understanding the complexity of the generic host‌

30.5 了解泛型主机的复杂性

Before .NET 6, all ASP.NET Core apps used the generic host and Startup approach. Many people liked the consistent structure this added, but it also has some drawbacks and complexity:
在 .NET 6 之前,所有 ASP.NET Core 应用程序都使用通用主机和启动方法。许多人喜欢它添加的一致结构,但它也有一些缺点和复杂性:

• Configuration is split between two files.
配置在两个文件之间拆分。

• The separation between Program.cs and Startup is somewhat arbitrary.
Program.cs 和 Startup 之间的划分有些武断。

• The generic IHostBuilder exposes newcomers to legacy decisions.
通用 IHostBuilder 使新人能够接触到传统决策。

• The lambda-based configuration can be hard to follow and reason about.
基于 lambda 的配置可能难以遵循和推理。

• The pattern-based conventions of Startup may be hard to discover.
Startup 的基于模式的约定可能很难发现。

• Tooling historically relies on your defining a CreateHostBuilder method in Program.cs.
工具以前依赖于您在 Program.cs 中定义 CreateHostBuilder 方法。

I’ll address each of these problems in turn and afterward discuss how WebApplication attempted to improve the situation.
我将依次解决这些问题中的每一个,然后讨论 WebApplication 如何尝试改善这种情况。

Points 1 and 2 in the preceding list deal with the separation between Program.cs and Startup. As you saw in section 30.1, theoretically the intention is that Program.cs defines the host and rarely changes, whereas Startup defines the app features (services, middleware, and endpoints). This seems like a reasonable decision, but one inevitable downside is that you need to flick back and forth between at least two files to understand all your bootstrapping code.
前面列表中的第 1 点和第 2 点涉及 Program.cs 和 Startup 之间的分离。正如您在 Section 30.1 中看到的,理论上的目的是 Program.cs 定义主机并且很少更改,而 Startup 定义应用程序功能(服务、中间件和端点)。这似乎是一个合理的决定,但一个不可避免的缺点是,您需要在至少两个文件之间来回切换才能理解所有引导代码。

On top of that, you don’t necessarily need to stick to these conventions. You can register services in Program.cs by calling HostBuilder.ConfigureServices(), for example, or register middleware using WebHostBuilder.Configure(). This is relatively rare but not entirely unheard-of, further blurring the lines between the files.
最重要的是,您不一定需要遵守这些约定。例如,您可以通过调用 HostBuilder.ConfigureServices() 在 Program.cs 中注册服务,或使用 WebHostBuilder.Configure() 注册中间件。这种情况相对罕见,但并非完全闻所未闻,进一步模糊了文件之间的界限。

Point 3 relates to the fact that you must call ConfigureWebHostDefaults() (which uses an IWebHostBuilder) to set up Kestrel and register your Startup class. This level of indirection (and the introduction of another builder type) is a remnant of decisions harking back to ASP.NET Core 1.0. For people familiar with ASP.NET Core, this pattern is just one of those things, but it adds confusion when you’re new to it.
第 3 点与必须调用 ConfigureWebHostDefaults()(使用 IWebHostBuilder)来设置 Kestrel 并注册 Startup 类这一事实有关。这种间接级别(以及另一种构建器类型的引入)是可以追溯到 ASP.NET Core 1.0 的决策的残余。对于熟悉 ASP.NET Core 的人来说,这种模式只是其中之一,但当你刚接触它时,它会增加困惑。

NOTE For a walk-through of the evolution of ASP.NET Core bootstrapping code, see my blog post at https://andrewlock.net/exploring-dotnet-6-part-2-comparing-webapplicationbuilder-to-the-generic-host/ .
注意有关 ASP.NET Core 引导代码演变的演练,请参阅我在 https://andrewlock.net/exploring-dotnet-6-part-2-comparing-webapplicationbuilder-to-the-generic-host/ 上的博客文章。

Similarly, the lambda-based configuration mentioned in point 4 can be hard for newcomers to ASP.NET Core to follow. If you’re new to .NET, lambdas are an extra concept you’ll need to understand before you can understand the basics of the code. On top of that, the execution of the lambdas doesn’t necessarily happen sequentially; the HostBuilder essentially queues the lambda methods so they’re executed at the right time. Consider the following snippet:
同样,第 4 点中提到的基于 lambda 的配置对于 ASP.NET Core 的新手来说可能很难理解。如果您不熟悉 .NET,则 lambda 是一个额外的概念,您需要先了解,然后才能了解代码的基础知识。最重要的是,lambda 的执行不一定是按顺序发生的;HostBuilder 实质上是将 lambda 方法排队,以便它们在正确的时间执行。请考虑以下代码段:

public static IhostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging => logging.AddSeq())
.ConfigureAppConfiguration(config => {})
.ConfigureServices(s => {})
.ConfigureHostConfiguration(config => {})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});

The lambdas execute in the following order:
lambda 按以下顺序执行:

  1. ConfigureWebHostDefaults()
  2. ConfigureHostConfiguration()
  3. ConfigureAppConfiguration()
  4. ConfigureLogging()
  5. ConfigureServices()
  6. Startup.ConfigureServices()
  7. Startup.Configure()

For the most part, this ordering detail shouldn’t matter, but it still adds apparent complexity for those who are new to ASP.NET Core.
在大多数情况下,这个排序细节应该无关紧要,但对于刚接触 ASP.NET Core 的人来说,它仍然增加了明显的复杂性。

Point 5 in the list of challenges relates to the Startup class and the default convention/ pattern-based approach. Users coming to ASP.NET Core for the first time will likely be familiar with interfaces and base classes, but they may not have experienced the reflection-based approach.
挑战列表中的第 5 点与 Startup 类和默认的约定/基于模式的方法有关。首次使用 ASP.NET Core 的用户可能熟悉接口和基类,但他们可能没有体验过基于反射的方法。

Using conventions instead of an explicit interface adds flexibility but can make things harder for discoverability. There are also various caveats and edge cases to consider. For example, you can inject only IWebHostEnvironment and IConfiguration into the Startup constructor; you can’t inject anything into the ConfigureServices() method, but you can inject any registered service into Configure(). These are implied rules that you discover primarily by breaking them and then having your app shout at you!‌
使用约定而不是显式接口可以增加灵活性,但可能会使可发现性变得更加困难。还有各种注意事项和边缘情况需要考虑。例如,只能将 IWebHostEnvironment 和 IConfiguration 注入 Startup 构造函数;你不能向 ConfigureServices() 方法注入任何内容,但你可以将任何已注册的服务注入到 Configure() 中。这些是隐含的规则,您主要是通过打破它们,然后让您的应用程序对您大喊大叫来发现的!

TIP The pattern-based approach allows for a lot more than DI into Configure. You can also create environment-specific methods, such as Configure-DevelopmentServices or ConfigureProductionServices, and ASP.NET Core invokes the correct method based on the environment. You can even create a whole StartupProduction class if you wish! For more details on these Startup conventions, see the documentation at http://mng.bz/Oxxw.
提示:基于模式的方法允许的不仅仅是 DI 到 Configure 中。您还可以创建特定于环境的方法,例如 Configure-DevelopmentServices 或 ConfigureProductionServices,ASP.NET Core 会根据环境调用正确的方法。如果您愿意,您甚至可以创建整个 StartupProduction 类!有关这些 Startup 约定的更多详细信息,请参阅 http://mng.bz/Oxxw 中的文档。

The Startup class isn’t the only place where ASP.NET Core relies on opaque conventions. You may remember in section30.2 I mentioned that Program.cs deliberately extracts the building of the IHostBuilder to a method called CreateHostBuilder. The name of this method was historically important. Tooling such as the EF Core tools hooked into it so that they could load your application configuration and services when running migrations and other functionality. In earlier versions of ASP.NET Core, renaming this method would break all your tooling!
Startup 类并不是 ASP.NET Core 依赖于不透明约定的唯一位置。你可能还记得30.2节我提到Program.cs特意将 IHostBuilder 的构建提取到一个名为 CreateHostBuilder 的方法中。这种方法的名称在历史上很重要。EF Core 工具等工具挂接到其中,以便它们可以在运行迁移和其他功能时加载应用程序配置和服务。在早期版本的 ASP.NET Core 中,重命名此方法会破坏您的所有工具!

NOTE As of .NET 6, you don’t have to create a CreateHostBuilder method; you can create your whole app inside your Main function (or using top-level statements), and the EF Core tools will work without error. This was fixed partly to add support for WebApplication. If you’re interested in the mechanics of how it was fixed, see my blog at http://mng.bz/Y11z.
注意:从 .NET 6 开始,您不必创建 CreateHostBuilder 方法;您可以在 Main 函数中(或使用顶级语句)创建整个应用程序,EF Core 工具将正常工作而不会出错。此问题已部分修复,以添加对 WebApplication 的支持。如果您对修复它的机制感兴趣,请参阅我的博客 http://mng.bz/Y11z

Once you’re experienced with ASP.NET Core, most of these gripes become relatively minor. You quickly get used to the standard patterns and avoid the pitfalls. But for new users of ASP.NET Core, Microsoft wanted a smoother experience, closer to the experience you get in many other languages.
一旦您体验了 ASP.NET Core,这些抱怨中的大多数都会变得相对较小。您很快就会习惯标准模式并避免陷阱。但对于 ASP.NET Core 的新用户,Microsoft 希望获得更流畅的体验,更接近您在许多其他语言中获得的体验。

The minimal hosting APIs provided by WebApplicationBuilder and WebApplication largely address these concerns. Configuration happens all in one file using an imperative style, with far fewer lambda-based configuration methods or implicit convention-based setup.
WebApplicationBuilder 和 WebApplication 提供的最小托管 API 在很大程度上解决了这些问题。使用命令式样式在一个文件中进行配置,基于 lambda 的配置方法或基于约定的隐式设置要少得多。

All the relevant objects like configuration and environment are exposed as properties on the WebApplicationBuilder or WebApplication types, so they’re easy to discover.‌
所有相关对象(如配置和环境)都作为 WebApplicationBuilder 或 WebApplication 类型的属性公开,因此很容易发现。

WebApplicationBuilder and WebApplication also try to hide much of the complexity and legacy decisions from you. Under the hood, WebApplication uses the generic host, but you don’t need to know that to use it or be productive. As you’ve seen throughout the book, WebApplication automatically adds various middleware to your pipeline, helping you avoid common pitfalls, such as incorrect middleware ordering.
WebApplicationBuilder 和 WebApplication 还试图向您隐藏许多复杂性和遗留决策。在后台,WebApplication 使用通用主机,但您无需知道它即可使用它或提高工作效率。正如您在整本书中所看到的,WebApplication 会自动将各种中间件添加到您的管道中,帮助您避免常见的陷阱,例如中间件顺序不正确。

NOTE If you’re interested in how WebApplicationBuilder abstracts over the generic host, see my post at https://andrewlock.net/exploring-dotnet-6-part-3-exploring-the-code-behind-webapplicationbuilder/ .
注意:如果您对 WebApplicationBuilder 如何在通用主机上进行抽象感兴趣,请参阅我在 https://andrewlock.net/exploring-dotnet-6-part-3-exploring-the-code-behind-webapplicationbuilder/ 上的帖子。

In most cases, minimal hosting provides an easier bootstrapping experience to the generic host and Startup, and Microsoft considers it to be the modern way to create ASP.NET Core apps. But there are cases in which you might want to consider using the generic host instead.
在大多数情况下,最小托管为通用主机和启动提供了更轻松的引导体验,Microsoft 认为这是创建 ASP.NET Core 应用程序的现代方式。但在某些情况下,您可能需要考虑改用通用主机。

30.6 Choosing between the generic host and minimal hosting‌

30.6 在通用主机和最小主机之间进行选择

The introduction of WebApplication and WebApplicationBuilder in .NET 6, also known as minimal hosting, was intended to provide a dramatically simpler “getting started” experience for newcomers to .NET and ASP.NET Core. All the built-in ASP.NET Core templates use minimal hosting now, and in most cases there’s little reason to look back. In this section I discuss some of the cases in which you might still want to use the generic host approach.
在 .NET 6 中引入 WebApplication 和 WebApplicationBuilder(也称为最小托管),旨在为 .NET 和 ASP.NET Core 的新手提供极其简单的“入门”体验。所有内置的 ASP.NET Core 模板现在都使用最少的托管,在大多数情况下,几乎没有理由回顾过去。在本节中,我将讨论您可能仍希望使用通用主机方法的一些情况。

In three main cases, you’ll likely want to stick with the generic host instead of using minimal hosting with WebApplication:
在三种主要情况下,您可能希望坚持使用通用主机,而不是对 WebApplication 使用最小托管:

• When you already have an ASP.NET Core application that uses the generic host
当您已有使用通用主机的 ASP.NET Core 应用程序时

• When you need (or want) fine control of building the IHost object
当您需要 (或想要) 精细控制构建 IHost 对象时

• When you’re creating a non-HTTP application
当您创建非 HTTP 应用程序时

The first use case is relatively obvious: if you already have an ASP.NET Core app that uses the generic host and Startup, you don’t need to change it. You can still upgrade your app to .NET 7, and you shouldn’t need to change any of your startup code. The generic host and Startup are fully supported in .NET 7, but they’re not the default experience.
第一个用例相对明显:如果您已经有一个使用通用主机和 Startup 的 ASP.NET Core 应用程序,则无需更改它。您仍然可以将应用程序升级到 .NET 7,并且不需要更改任何启动代码。.NET 7 完全支持泛型主机和启动,但它们不是默认体验。

TIP In many cases, upgrading an existing project to .NET 7 simply requires updating the framework in the .csproj file and updating some NuGet packages. If you’re unlucky, you may find that some APIs have changed. Microsoft publishes upgrade guides for each major version release, so it’s worth reading these before upgrading your apps: https://learn.microsoft.com/zh-cn/aspnet/core/migration/60-70 .
提示在许多情况下,将现有项目升级到 .NET 7 只需要更新 .csproj 文件中的框架并更新一些 NuGet 包。运气不好的话,你可能会发现一些 API 已经发生了变化。Microsoft 发布了每个主要版本的升级指南,因此在升级应用程序之前,值得阅读这些指南:https://learn.microsoft.com/zh-cn/aspnet/core/migration/60-70

If you’re creating a new app, but for some reason you don’t like the default options used by WebApplicationBuilder, using the generic host may be your best option. I generally wouldn’t advise this approach, as it will likely require more maintenance than using WebApplication, but it does give you complete control of your bootstrap code if you need or want it.
如果您正在创建新应用程序,但出于某种原因您不喜欢 WebApplicationBuilder 使用的默认选项,则使用通用主机可能是您的最佳选择。我通常不建议使用这种方法,因为它可能需要比使用 WebApplication 更多的维护,但如果您需要或想要它,它确实可以让您完全控制引导代码。

The final case applies when you’re building an ASP.NET Core application that primarily runs background processing services, handling messages from a queue for example, but doesn’t handle HTTP requests. The minimal hosting WebApplication and WebApplicationBuilder are, as their names imply, focused on building web applications, so they don’t make sense in this situation.
当您构建主要运行后台处理服务(例如处理来自队列的消息,但不处理 HTTP 请求)的 ASP.NET Core 应用程序时,最后一种情况适用。顾名思义,最小托管 WebApplication 和 WebApplicationBuilder 专注于构建 Web 应用程序,因此在这种情况下它们没有意义。

NOTE You’ll learn how to create background tasks and services using the generic host in chapter 34. .NET 8 introduces a non-HTTP version of the WebApplicationBuilder called HostApplicationBuilder which aims to simplify app bootstrapping for your background services.
注意:您将在第 34 章中学习如何使用通用主机创建后台任务和服务。.NET 8 引入了一个名为 HostApplicationBuilder 的非 HTTP 版本的 WebApplicationBuilder,旨在简化后台服务的应用程序启动。

If you’re not in any of these situations, strongly consider using the minimal hosting WebApplication approach and the imperative, scriptlike bootstrapping of top-level statements.
如果您不处于上述任何一种情况,强烈建议使用最小托管 WebApplication 方法和顶级语句的命令式脚本式引导。

NOTE The fact that you’re using WebApplication doesn’t mean you have to dump all your service and middleware configuration into Program.cs. For alternative approaches, such as using a Startup class you invoke manually or local functions to separate your configuration, see my blog post at https://andrewlock.net/exploring-dotnet-6-part-12-upgrading-a-dotnet-5-startup-based-app-to-dotnet-6/ .
注意:您使用的是 WebApplication 这一事实并不意味着您必须将所有服务和中间件配置转储到 Program.cs 中。有关替代方法,例如使用手动调用的 Startup 类或本地函数来分隔配置,请参阅我在 https://andrewlock.net/exploring-dotnet-6-part-12-upgrading-a-dotnet-5-startup-based-app-to-dotnet-6/ 上的博客文章。

In this chapter I provided a relatively quick overview of the generic host and Startup-based approach. If you’re thinking of moving from the generic host to minimal hosting, or if you’re familiar with minimal hosting but need to work with the generic host, you may find yourself looking around for an equivalent feature in the other hosting model. The documentation for migrating from .NET 5 to .NET 6 provides a good description of the differences between the two models, and how each individual feature has changed. You can find it at https://learn.microsoft.com/zh-cn/aspnet/core/migration/50-to-60.
在本章中,我相对快速地概述了通用主机和基于 Startup 的方法。如果您正在考虑从通用主机迁移到最小托管,或者如果您熟悉最小托管但需要与通用主机合作,您可能会发现自己在另一种托管模型中寻找等效功能。从 .NET 5 迁移到 .NET 6 的文档很好地描述了两种模型之间的差异,以及每个单独的功能是如何变化的。您可以在 https://learn.microsoft.com/zh-cn/aspnet/core/migration/50-to-60 找到它。

TIP Alternatively, David Fowler from the .NET team has a similar cheat sheet describing the migration. See https://gist.github.com/davidfowl/0e0372c3c1d895c3ce195ba983b1e03d .
提示:或者,来自 .NET 团队的 David Fowler 有一个类似的备忘单来描述迁移。请参阅 https://gist.github.com/davidfowl/0e0372c3c1d895c3ce195ba983b1e03d

Whether you choose to use the generic host or minimal hosting, all the same ASP.NET Core concepts are there: configuration, middleware, and DI. In the next chapter you’ll learn about some more advanced uses of each of these concepts, such as creating branching middleware pipelines and custom DI containers.
无论您选择使用通用主机还是最小托管,所有相同的 ASP.NET Core 概念都存在:配置、中间件和 DI。在下一章中,您将了解这些概念的一些更高级的用法,例如创建分支中间件管道和自定义 DI 容器。

30.7 Summary

30.7 总结

Before .NET 6, ASP.NET Core apps split configuration between two files: Program.cs and Startup.cs. Program.cs contains the entry point for the app and is used to configure and build a IHost object. Startup is where you configure the DI container, middleware pipeline, and endpoints for your app.
在 .NET 6 之前,ASP.NET Core 应用程序将配置拆分为两个文件:Program.cs 和 Startup.cs。Program.cs 包含应用程序的入口点,用于配置和生成 IHost 对象。Startup (启动) 是您为应用程序配置 DI 容器、中间件管道和终端节点的地方。

The Program class typically contains a method called CreateHostBuilder(), which creates an IHostBuilder instance. The Main entry point invokes CreateHostBuilder(), calls IHostBuilder.Build() to create an instance of IHost, and finally runs the app by calling IHost.Run().
Program 类通常包含一个名为 CreateHostBuilder() 的方法,该方法创建一个 IHostBuilder 实例。主入口点调用 CreateHostBuilder(),调用 IHostBuilder.Build() 来创建 IHost 的实例,最后通过调用 IHost.Run() 运行应用程序。

You can create an IHostBuilder by calling Host.CreateDefaultBuilder(). This creates a HostBuilder instance using the default configuration, similar to the configuration used when calling WebApplication.CreateBuilder(). The default HostBuilder uses default logging and configuration providers, configures the hosting environment based on environment variables and command-line arguments, and configures the DI container settings.
您可以通过调用 Host.CreateDefaultBuilder() 来创建 IHostBuilder。这将使用默认配置创建一个 HostBuilder 实例,类似于调用 WebApplication.CreateBuilder() 时使用的配置。默认 HostBuilder 使用默认日志记录和配置提供程序,根据环境变量和命令行参数配置托管环境,并配置 DI 容器设置。

ASP.NET Core apps using the generic host typically call ConfigureWebHostDefaults(), on the HostBuilder, providing a lambda that calls UseStartup<Startup>() on an IWebHostBuilder instance. This tells the HostBuilder to configure the DI container and middleware pipeline based on the Startup class.
使用通用主机的 ASP.NET Core 应用程序通常在 HostBuilder 上调用 ConfigureWebHostDefaults(),从而提供在 IWebHostBuilder 实例上调用 UseStartup<Startup>() 的 lambda。这会告诉 HostBuilder 根据 Startup 类配置 DI 容器和中间件管道。

Use the Startup class to register services with DI, configure your middleware pipeline, and register your endpoints. It is a conventional class, in that it doesn’t have to implement an interface or base class. Instead, the IHostBuilder looks for specific named methods to invoke using reflection.
使用 Startup 类向 DI 注册服务、配置中间件管道并注册终端节点。它是一个约定俗成的类,因为它不必实现接口或基类。相反,IHostBuilder 会查找要使用反射调用的特定命名方法。

Register your DI services in the ConfigureServices(IServiceCollection) method of Startup. You register services using the same Add methods you use to register services on WebApplicationBuilder.Services when using minimal hosting.
在 Startup 的 ConfigureServices(IServiceCollection) 方法中注册 DI 服务。使用最小托管时,您可以使用在 WebApplicationBuilder.Services 上注册服务时使用的相同 Add
方法注册服务。

If you need to access your app’s IConfiguration or IWebHostEnvironment (exposed as Configuration and Environment, respectively, on WebApplicationBuilder), you can inject them into your Startup constructor.You can’t inject any other services into the Startup constructor.
如果需要访问应用程序的 IConfiguration 或 IWebHostEnvironment(在 WebApplicationBuilder 上分别作为 Configuration 和 Environment 公开),则可以将它们注入到 Startup 构造函数中。您不能将任何其他服务注入 Startup 构造函数。

Register your middleware pipeline in Startup.Configure(IApplicationBuilder). Use the same Use methods you use with WebApplication to add middleware to the pipeline. As for WebApplication, the order in which you add the middleware defines their order in the pipeline.
在 Startup.Configure (IApplicationBuilder) 中注册中间件管道。使用与 WebApplication 相同的 Use
方法将中间件添加到管道中。对于 WebApplication,您添加中间件的顺序定义了它们在管道中的顺序。

WebApplication automatically adds middleware such as the routing middleware and endpoint middleware to the pipeline when you’re using minimal hosting. When using Startup, you must explicitly add this middleware yourself.
当您使用最小托管时,WebApplication 会自动将中间件(如路由中间件和终端节点中间件)添加到管道中。使用 Startup 时,您必须自己显式添加此中间件。

To register endpoints, call UseEndpoints(endpoints => {}) and call the appropriate Map functions on the provided IEndpointRouteBuilder in the lambda function. This differs significantly from minimal hosting, in which you can call Map directly on the WebApplication instance.
要注册终端节点,请调用 UseEndpoints(endpoints => {}) 并在 lambda 函数中提供的 IEndpointRouteBuilder 上调用相应的 Map 函数。这与最小托管有很大不同,在最小托管中,您可以直接在 WebApplication 实例上调用 Map。

You can customize the IHostBuilder instance by adding configuration methods such as ConfigureLogging() or ConfigureAppConfiguration(). These methods run after any previous invocations, adding extra layers of configuration to the IHostBuilder instance.
您可以通过添加配置方法(如 ConfigureLogging() 或 ConfigureAppConfiguration())来自定义 IHostBuilder 实例。这些方法在之前的任何调用之后运行,向 IHostBuilder 实例添加额外的配置层。

The generic host is flexible but has greater inherent complexity due to its deferred execution style, extensive use of lambda methods, and heavy use of convention. Minimal hosting aimed to simplify the bootstrapping code to make it more imperative, reducing much of the indirection.
泛型主机很灵活,但由于其延迟执行样式、广泛使用 lambda 方法和大量使用约定,因此具有更大的固有复杂性。最小托管旨在简化引导代码,使其更加必要,从而减少大部分间接性。

Minimal hosting enforces more defaults but is generally easier to work with for newcomers to ASP.NET Core.
最小托管强制实施更多默认值,但通常更容易使用 ASP.NET Core 的新手。

If you already have an ASP.NET Core application using Startup and the generic host, there’s no need to switch to using WebApplication and minimal hosting; the generic host is fully supported in .NET 7. Additionally, if you’re creating a non- HTTP application, the generic host is currently the best option.
如果您已经拥有使用 Startup 和通用主机的 ASP.NET Core 应用程序,则无需切换到使用 WebApplication 和最小托管;.NET 7 完全支持泛型主机。此外,如果要创建非 HTTP 应用程序,则通用主机是当前最佳选项。

If you’re creating a new ASP.NET Core application, minimal hosting will likely provide a smoother experience. You should generally favor it over the generic host for new apps unless you need fine control of the IHostBuilder configuration.
如果您正在创建新的 ASP.NET Core 应用程序,最小托管可能会提供更流畅的体验。对于新应用程序,您通常应该更喜欢它而不是通用主机,除非您需要对 IHostBuilder 配置进行精细控制。

Leave a Reply

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