Ultimate ASP.NET Core Web API 2nd Premium Edition
Ultimate ASP.NET Core Web API 1 PROJECT CONFIGURATION
1 项目配置
Ultimate ASP.NET Core Web API 2 Creating the Required Projects
2 创建所需的项目
Ultimate ASP.NET Core Web API 3 ONION ARCHITECTURE IMPLEMENTATION
3 洋葱架构实现
Ultimate ASP.NET Core Web API 4 HANDLING GET REQUESTS
4 处理 GET 请求
Ultimate ASP.NET Core Web API 5 GLOBAL ERROR HANDLING
5 全局错误处理
Ultimate ASP.NET Core Web API 6 GETTING ADDITIONAL RESOURCES
6 获取资源
Ultimate ASP.NET Core Web API 7 CONTENT NEGOTIATION
7 内容协商
Ultimate ASP.NET Core Web API 8 METHOD SAFETY AND METHOD IDEMPOTENCY
8 方法安全和方法幂等性
Ultimate ASP.NET Core Web API 9 CREATING RESOURCES
9 创建资源
Ultimate ASP.NET Core Web API 10 WORKING WITH DELETE REQUESTS
10 使用 DELETE 请求
Ultimate ASP.NET Core Web API 11 WORKING WITH PUT REQUESTS
11 使用 PUT 请求
Ultimate ASP.NET Core Web API 12 WORKING WITH PATCH REQUESTS
12 使用PATCH请求
Ultimate ASP.NET Core Web API 13 VALIDATION
13 验证
Ultimate ASP.NET Core Web API 14 ASYNCHRONOUS CODE
14 异步代码
Ultimate ASP.NET Core Web API 15 ACTION FILTERS
15 动作过滤器
Ultimate ASP.NET Core Web API 16 PAGING
16 分页
Ultimate ASP.NET Core Web API 17 FILTERING
17 过滤
Ultimate ASP.NET Core Web API 18 SEARCHING
18 搜索
Ultimate ASP.NET Core Web API 19 SORTING
19 排序
Ultimate ASP.NET Core Web API 20 DATA SHAPING
20 数据整形
Ultimate ASP.NET Core Web API 21 SUPPORTING HATEOAS
21 支持 HATEOAS
Ultimate ASP.NET Core Web API 22 WORKING WITH OPTIONS AND HEAD REQUESTS
22 使用 OPTIONS 和 HEAD 请求
Ultimate ASP.NET Core Web API 23 ROOT DOCUMENT
23 根文档
Ultimate ASP.NET Core Web API 24 VERSIONING APIS
24 API版本控制
Ultimate ASP.NET Core Web API 25 CACHING
25 缓存
Ultimate ASP.NET Core Web API 26 RATE LIMITING AND THROTTLING
26 速率限制
Ultimate ASP.NET Core Web API 27 JWT, IDENTITY, AND REFRESH TOKEN
27 个 JWT、身份和刷新令牌
Ultimate ASP.NET Core Web API 28 REFRESH TOKEN
28 刷新令牌
Ultimate ASP.NET Core Web API 29 BINDING CONFIGURATION AND OPTIONS PATTERN
29 绑定配置和选项模式
Ultimate ASP.NET Core Web API 30 DOCUMENTING API WITH SWAGGER
30 使用 SWAGGER 编写 API 文档
Ultimate ASP.NET Core Web API 31 DEPLOYMENT TO IIS
31 部署到 IIS
Ultimate ASP.NET Core Web API 32 BONUS 1 - RESPONSE PERFORMANCE IMPROVEMENTS
32 赠送章节 1 - 响应性能改进
Ultimate ASP.NET Core Web API 33 BONUS 2 - INTRODUCTION TO CQRS AND MEDIATR WITH ASP.NET CORE WEB API
33 赠送章节 2 - 使用 ASP.NET 核心 WEB API 的 CQRS 和 MEDIATR 简介
1 Project configuration
1 项目配置
Configuration in .NET Core is very different from what we’re used to in .NET Framework projects. We don’t use the web.config file anymore, but instead, use a built-in Configuration framework that comes out of the box in .NET Core.
配置.NET Core 与我们习惯的 .NET Framework 不同,不再使用 web.config 文件,而是使用开箱即用的内置配置框架。
To be able to develop good applications, we need to understand how to configure our application and its services first.
为了能够开发好的应用程序,需要首先了解如何配置应用程序及其服务。
In this section, we’ll learn about configuration in the Program class and set up our application. We will also learn how to register different services and how to use extension methods to achieve this.
在本节中,将了解 Program 类中的配置并设置应用程序。还将学习注册不同的服务,以及使用扩展方法来实现这一点。
Of course, the first thing we need to do is to create a new project, so,let’s dive right into it.
当然,需要做的第一件事是创建一个新项目,所以,直接开始吧。
1.1 Creating a New Project
1.1 创建新项目
Let's open Visual Studio, we are going to use VS 2022, and create a new ASP.NET Core Web API Application:
打开Visual Studio,我们将使用 VS 2022,创建一个新的 ASP.NET Core Web API 应用程序:
Now let’s choose a name and location for our project:
给项目选择一个名称和位置:
Next, we want to choose a .NET 6.0 from the dropdown list. Also, we don’t want to enable OpenAPI support right now. We’ll do that later in the book on our own. Now we can proceed by clicking the Create button and the project will start initializing:
接下来,我们要从下拉列表中选择 .NET 6.0。此外,我们现在不想启用 OpenAPI 支持。我们将在本书的后面自己做这件事。现在,我们可以通过单击 Create 按钮继续,项目将开始初始化:
1.2 launchSettings.json File Configuration
1.2 launchSettings.json 文件配置
After the project has been created, we are going to modify the launchSettings.json file, which can be found in the Properties section of the Solution Explorer window.
创建项目后,我们将修改 launchSettings.json 文件,该文件可以在 Solution Explorer 窗口的 Properties 部分找到。
This configuration determines the launch behavior of the ASP.NET Core applications. As we can see, it contains both configurations to launch settings for IIS and self-hosted applications (Kestrel).
此配置决定了 ASP.NET Core 应用程序的启动行为。正如我们所看到的,它包含用于启动 IIS 设置的配置和自托管应用程序 (Kestrel)。
For now, let’s change the launchBrowser property to false to prevent the web browser from launching on application start.
现在,让我们将 launchBrowser 属性更改为 false,以防止 Web 浏览器在应用程序启动时启动。
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:1629",
"sslPort": 44370
}
},
"profiles": {
"CompanyEmployees": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"launchUrl": "weatherforecast",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "weatherforecast",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
This is convenient since we are developing a Web API project and we don’t need a browser to check our API out. We will use Postman (described later) for this purpose.
这很方便,因为我们正在开发一个 Web API 项目,我们不需要浏览器来检查我们的 API。为此,我们将使用 Postman(稍后介绍)。
If you’ve checked Configure for HTTPS checkbox earlier in the setup phase, you will end up with two URLs in the applicationUrl section — one for HTTPS (localhost:5001), and one for HTTP (localhost:5000).
如果您在创建项目时选中了 Configure for HTTPS 复选框,则 applicationUrl 部分最终会有两个 URL——一个用于 HTTPS (localhost:5001),一个用于 HTTP (localhost:5000)。
You’ll also notice the sslPort property which indicates that our application, when running in IISExpress, will be configured for HTTPS (port 44370), too.
您还会注意到 sslPort 属性,该属性指示我们的应用程序在 IISExpress 中运行时,也将配置为 HTTPS(端口 44370)。
NOTE: This HTTPS configuration is only valid in the local environment. You will have to configure a valid certificate and HTTPS redirection once you deploy the application.
注意:此 HTTPS 配置仅在本地环境中有效。部署应用程序后,您必须配置有效的证书和 HTTPS 重定向。
There is one more useful property for developing applications locally and that’s the launchUrl property. This property determines which URL will the application navigate to initially. For launchUrl property to work, we need to set the launchBrowser property to true. So, for example, if we set the launchUrl property to weatherforecast, we will be redirected to https://localhost:5001/weatherforecast when we launch our application.
还有一个用于在本地开发应用程序的有用属性,即 launchUrl 属性。此属性确定应用程序最初将导航到哪个 URL。要使 launchUrl 属性正常工作,我们需要将 launchBrowser 属性设置为 true。因此,例如,如果我们将 launchUrl 属性设置为 weatherforecast,则当我们启动应用程序时,我们将重定向到 https://localhost:5001/weatherforecast。
1.3 Program.cs Class Explanations
1.3 Program.cs 类说明
Program.cs is the entry point to our application and it looks like this:
Program.cs 是我们应用程序的入口点,它看起来像这样:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
Compared to the Program.cs class from .NET 5, there are some major changes. Some of the most obvious are:
与 .NET 5 中的 Program.cs 类相比,有一些重大变化。一些最明显的是:
• Top-level statements
• 顶级语句
• Implicit using directives
• 隐式 using 指令
• No Startup class (on the project level)
• 无 Startup 类(在项目级别)
“Top-level statements” means the compiler generates the namespace, class, and method elements for the main program in our application. We can see that we don’t have the class block in the code nor the Main method. All of that is generated for us by the compiler. Of course, we can add other functions to the Program class and those will be created as the local functions nested inside the generated Main method. Top-level statements are meant to simplify the entry point to the application and remove the extra “fluff” so we can focus on the important stuff instead.
“顶级语句”是指编译器为应用程序中的主程序生成命名空间、类和方法元素。我们可以看到,代码中没有 class 块,也没有 Main 方法。所有这些都是由编译器为我们生成的。当然,我们可以向 Program 类添加其他函数,这些函数将创建为嵌套在生成的 Main 方法中的本地函数。顶级语句旨在简化应用程序的入口点并删除额外的 “绒毛”,以便我们可以专注于重要的东西。
“Implicit using directives” mean the compiler automatically adds a different set of using directives based on a project type, so we don’t have to do that manually. These using directives are stored in the obj/Debug/net6.0 folder of our project under the name CompanyEmployees.GlobalUsings.g.cs:
“隐式 using 指令”意味着编译器会根据项目类型自动添加一组不同的 using 指令,因此我们不必手动执行此作。这些 using 指令存储在项目的 obj/Debug/net6.0 文件夹中,名称为 CompanyEmployees.GlobalUsings.g.cs:
// <auto-generated/>
global using global::Microsoft.AspNetCore.Builder;
global using global::Microsoft.AspNetCore.Hosting;
global using global::Microsoft.AspNetCore.Http;
global using global::Microsoft.AspNetCore.Routing;
global using global::Microsoft.Extensions.Configuration;
global using global::Microsoft.Extensions.DependencyInjection;
global using global::Microsoft.Extensions.Hosting;
global using global::Microsoft.Extensions.Logging;
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Net.Http.Json;
global using global::System.Threading;
global using global::System.Threading.Tasks;
This means that we can use different classes from these namespaces in our project without adding using directives explicitly in our project files. Of course, if you don’t want this type of behavior, you can turn it off by visiting the project file and disabling the ImplicitUsings tag:
这意味着我们可以在项目中使用来自这些命名空间的不同类,而无需在项目文件中显式添加 using 指令。当然,如果您不希望出现此类行为,可以通过访问项目文件并禁用 ImplicitUsings 标签来关闭它:
<ImplicitUsings>disable</ImplicitUsings>
By default, this is enabled in the .csproj file, and we are going to keep it like that.
默认情况下,这在 .csproj 文件中处于启用状态,我们将保持这种状态(不要去修改它)。
Now, let’s take a look at the code inside the Program class. With this line of code:
现在,让我们看看 Program 类中的代码。使用这行代码:
var builder = WebApplication.CreateBuilder(args);
The application creates a builder variable of the type WebApplicationBuilder. The WebApplicationBuilder class is responsible for four main things:
应用程序将创建一个 WebApplicationBuilder 类型的 builder 变量。WebApplicationBuilder 类负责四个主要任务:
• Adding Configuration to the project by using the builder.Configuration property
• 使用builder.Configuration构建器将配置添加到项目中
• Registering services in our app with the builder.Services property
• 使用builder.Services构建器在我们的应用程序中注册服务
• Logging configuration with the builder.Logging property
• 使用builder.Logging生成器进行日志记录配置
• Other IHostBuilder and IWebHostBuilder configuration
• 其他 IHostBuilder 和 IWebHostBuilder 配置
Compared to .NET 5 where we had a static CreateDefaultBuilder class, which returned the IHostBuilder type, now we have the static CreateBuilder method, which returns WebApplicationBuilder type.
与 .NET 5 相比,我们有一个静态 CreateDefaultBuilder 类,它返回 IHostBuilder 类型,现在我们有静态 CreateBuilder 方法,它返回 WebApplicationBuilder 类型。
Of course, as we see it, we don’t have the Startup class with two familiar methods: ConfigureServices and Configure. Now, all this is replaced by the code inside the Program.cs file.
当然,正如我们所看到的,我们没有包含两个熟悉方法的 Startup 类:ConfigureServices 和 Configure。现在,所有这些都被 Program.cs 文件中的代码替换了。
Since we don’t have the ConfigureServices method to configure our services, we can do that right below the builder variable declaration. In the new template, there’s even a comment section suggesting where we should start with service registration. A service is a reusable part of the code that adds some functionality to our application, but we’ll talk about services more later on.
由于我们没有 ConfigureServices 方法来配置我们的服务,因此我们可以在 builder 变量声明的正下方执行此作。在新模板中,甚至还有一个注释部分,建议我们应该从哪里开始服务注册。服务是代码的可重用部分,它为应用程序添加了一些功能,但我们稍后将更多地讨论服务。
In .NET 5, we would use the Configure method to add different middleware components to the application’s request pipeline. But since we don’t have that method anymore, we can use the section below the var app = builder.Build(); part to do that. Again, this is marked with the comment section as well:
在 .NET 5 中,我们将使用 Configure 方法将不同的中间件组件添加到应用程序的请求管道中。但是由于我们不再有该方法,因此我们可以使用 var app = builder.Build(); 部分来做到这一点。同样,这也用评论部分标记:
NOTE: If you still want to create your application using the .NET 5 way, with Program and Startup classes, you can do that, .NET 6 supports it as well. The easiest way is to create a .NET 5 project, copy the Startup and Program classes and paste it into the .NET 6 project.
注意:如果您仍希望使用 .NET 5 方式创建应用程序,使用 Program 和 Startup 类,您可以这样做,.NET 6 也支持它。最简单的方法是创建一个 .NET 5 项目,复制 Startup 和 Program 类并将其粘贴到 .NET 6 项目中。
Since larger applications could potentially contain a lot of different services, we can end up with a lot of clutter and unreadable code in the Program class. To make it more readable for the next person and ourselves, we can structure the code into logical blocks and separate those blocks into extension methods.
由于较大的应用程序可能包含许多不同的服务,因此我们最终会在 Program 类中得到很多杂乱和不可读的代码。为了让下一个人和我们自己更具可读性,我们可以将代码构建成逻辑块,并将这些块分离到扩展方法中。
1.4 Extension Methods and CORS Configuration
1.4 扩展方法和 CORS 配置
An extension method is inherently a static method. What makes it different from other static methods is that it accepts this as the first parameter, and this represents the data type of the object which will be using that extension method. We’ll see what that means in a moment.
扩展方法本质上是一种静态方法。它与其他静态方法的不同之处在于,它接受 this 作为第一个参数,这表示将使用该扩展方法的对象的数据类型。我们稍后会看看这意味着什么。
An extension method must be defined inside a static class. This kind of method extends the behavior of a type in .NET. Once we define an extension method, it can be chained multiple times on the same type of object.
必须在 static 类中定义扩展方法。此方法扩展了 .NET 中类型的行为。一旦我们定义了一个扩展方法,它就可以在同一类型的对象上被多次链接。
So, let’s start writing some code to see how it all adds up.
那么,让我们开始编写一些代码,看看这一切是如何加起来的。
We are going to create a new folder Extensions in the project and create a new class inside that folder named ServiceExtensions. The ServiceExtensions class should be static.
我们将在项目中创建一个新文件夹 Extensions,并在该文件夹中创建一个名为 ServiceExtensions 的新类。ServiceExtensions 类应该是 static 类。
namespace CompanyEmployees.Extensions
{
public static class ServiceExtensions
{
}
}
Let’s start by implementing something we need for our project immediately so we can see how extensions work.
让我们立即开始实现项目所需的内容,以便了解扩展的工作原理。
The first thing we are going to do is to configure CORS in our application. CORS (Cross-Origin Resource Sharing) is a mechanism to give or restrict access rights to applications from different domains.
我们要做的第一件事是在我们的应用程序中配置 CORS。CORS(跨域资源共享)是一种机制,用于授予或限制来自不同域的应用程序的访问权限。
If we want to send requests from a different domain to our application, configuring CORS is mandatory. So, to start, we’ll add a code that allows all requests from all origins to be sent to our API:
如果我们想将请求从其他域发送到我们的应用程序,则必须配置 CORS。因此,首先,我们将添加一个代码,允许来自所有来源的所有请求都发送到我们的 API:
namespace CompanyEmployees.Extensions
{
public static class ServiceExtensions
{
public static void ConfigureCors(this IServiceCollection services) =>
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder =>
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
}
}
We are using basic CORS policy settings because allowing any origin, method, and header is okay for now. But we should be more restrictive with those settings in the production environment. More precisely, as restrictive as possible.
我们使用的是基本的 CORS 策略设置,因为允许任何源、方法和标头目前是可以的。但是我们应该在生产环境中对这些设置进行更多限制。更准确地说,尽可能严格。
Instead of the AllowAnyOrigin() method which allows requests from any source, we can use the WithOrigins("https://example.com") which will allow requests only from that concrete source. Also, instead of AllowAnyMethod() that allows all HTTP methods, we can use WithMethods("POST", "GET") that will allow only specific HTTP methods. Furthermore, you can make the same changes for the AllowAnyHeader() method by using, for example, the WithHeaders("accept", "content-type") method to allow only specific headers.
我们可以使用 WithOrigins("https://example.com") 方法,而不是允许来自任何源的请求的 AllowAnyOrigin() 方法,它将仅允许来自该具体源的请求。此外,我们可以使用 WithMethods("POST", "GET") 来只允许特定的 HTTP 方法,而不是允许所有 HTTP 方法的 AllowAnyMethod()。此外,您可以对 AllowAnyHeader() 方法进行相同的更改,例如,使用 WithHeaders("accept", "content-type")方法仅允许特定标头。
1.5 IIS Configuration
1.5 IIS 配置
ASP.NET Core applications are by default self-hosted, and if we want to host our application on IIS, we need to configure an IIS integration which will eventually help us with the deployment to IIS. To do that, we need to add the following code to the ServiceExtensions class:
默认情况下,ASP.NET Core 应用程序是自托管的,如果我们想在 IIS 上托管应用程序,则需要配置 IIS 集成,这最终将帮助我们部署到 IIS。为此,我们需要将以下代码添加到 ServiceExtensions 类中:
namespace CompanyEmployees.Extensions
{
public static class ServiceExtensions
{
public static void ConfigureCors(this IServiceCollection services) =>
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder =>
builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader());
});
public static void ConfigureIISIntegration(this IServiceCollection services) =>
services.Configure<IISOptions>(options =>
{
});
}
}
We do not initialize any of the properties inside the options because we are fine with the default values for now. But if you need to fine-tune the configuration right away, you might want to take a look at the possible options:
我们没有初始化选项中的任何属性,因为我们现在对默认值没有问题。但是,如果您需要立即微调配置,则可能需要查看可能的选项:
Option | Default | Setting |
---|---|---|
AutomaticAuthentication | true | if true,the authentication middleware sets the HttpContext.User and responds to generic challenges,if false,the authentication middleware only provides an identity(HttpContext.User ) and responds to challenges when explicitly requested by the AuthenticationScheme . Windows Authentication must be enabled in IIS for AutomaticAuthentication to function. 如果为 true,则身份验证中间件设置 HttpContext.User 并响应一般质询,如果为 false,则身份验证中间件仅提供 identity(HttpContext.User ),并在 AuthenticationScheme 显式请求时响应质询。必须在 IIS 中启用 Windows 身份验证,AutomaticAuthentication 才能正常工作。 |
AuthenticationDisplayName | null | Sets the display name shown to users on login pages 设置在登录页面上向用户显示的显示名称 |
ForwardClientCertificate | true | if true and the MS-ASPNETCORE-CLIENTCERT request header is present,the HttpContext.Connection.ClientCertificate is populated. 如果 true 且存在 MS-ASPNETCORE-CLIENTCERT 请求标头,则填充 HttpContext.Connection.ClientCertificate。 |
Now, we mentioned extension methods are great for organizing your code and extending functionalities. Let’s go back to our Program class and modify it to support CORS and IIS integration now that we’ve written extension methods for those functionalities. We are going to remove the first comment and write our code over it:
现在,我们提到了扩展方法非常适合组织代码和扩展功能。让我们回到我们的 Program 类并对其进行修改以支持 CORS 和 IIS 集成,因为我们已经为这些功能编写了扩展方法。我们将删除第一个注释并在其上编写我们的代码:
using CompanyEmployees.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.ConfigureCors();
builder.Services.ConfigureIISIntegration();
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
And let's add a few mandatory methods to the second part of the Program class (the one for the request pipeline configuration):
让我们向 Program 类的第二部分(用于请求管道配置的那个)添加一些强制性方法:
using CompanyEmployees.Extensions;
using Microsoft.AspNetCore.HttpOverrides;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.ConfigureCors();
builder.Services.ConfigureIISIntegration();
builder.Services.AddControllers();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.All
});
app.UseCors("CorsPolicy");
app.UseAuthorization();
app.MapControllers();
app.Run();
We’ve added CORS and IIS configuration to the section where we need to configure our services. Furthermore, CORS configuration has been added to the application’s pipeline inside the second part of the Program class.But as you can see, there are some additional methods unrelated to IIS configuration. Let’s go through those and learn what they do.
我们已将 CORS 和 IIS 配置添加到需要配置服务的部分。此外,CORS 配置已添加到 Program 类的第二部分内的应用程序管道中。但正如你所看到的,还有一些与 IIS 配置无关的其他方法。让我们来了解一下这些并了解它们的作用。
• app.UseForwardedHeaders() will forward proxy headers to the current request. This will help us during application deployment. Pay attention that we require Microsoft.AspNetCore.HttpOverrides using directive to introduce the ForwardedHeaders enumeration
•app.UseForwardedHeaders()会将代理标头转发到当前请求。这将在应用程序部署期间帮助我们。请注意,我们需要 Microsoft.AspNetCore.HttpOverrides using 指令来引入 ForwardedHeaders 枚举
• app.UseStaticFiles() enables using static files for the request. If we don’t set a path to the static files directory, it will use a wwwroot folder in our project by default.
•app.UseStaticFiles() 允许对请求使用静态文件。如果我们没有设置静态文件目录的路径,它将默认使用我们项目中的 wwwroot 文件夹。
• app.UseHsts() will add middleware for using HSTS, which adds the Strict-Transport-Security header.
• app.UseHsts() 将添加用于使用 HSTS 的中间件,从而添加 Strict-Transport-Security 标头。
1.6 Additional Code in the Program Class
1.6 Program 类中的其他代码
We have to pay attention to the AddControllers() method. This method registers only the controllers in IServiceCollection and not Views or Pages because they are not required in the Web API project which we are building.
我们必须注意 AddControllers() 方法。此方法仅在 IServiceCollection 中注册控制器,而不在 Views 或 Pages 中注册控制器,因为我们正在构建的 Web API 项目中不需要它们。
Right below the controller registration, we have this line of code:
在控制器注册的正下方,我们有这行代码:
var app = builder.Build();
With the Build method, we are creating the app variable of the type WebApplication. This class (WebApplication) is very important since it implements multiple interfaces like IHost that we can use to start and stop the host, IApplicationBuilder that we use to build the middleware pipeline (as you could’ve seen from our previous custom code), and IEndpointRouteBuilder used to add endpoints in our app.
使用 Build 方法,我们将创建 WebApplication 类型的 app 变量。这个类 (WebApplication) 非常重要,因为它实现了多个接口,例如我们可用于启动和停止主机的 IHost、用于构建中间件管道的 IApplicationBuilder(正如您从之前的自定义代码中所看到的那样),以及用于在应用程序中添加端点的 IEndpointRouteBuilder。
The UseHttpRedirection method is used to add the middleware for the redirection from HTTP to HTTPS. Also, we can see the UseAuthorization method that adds the authorization middleware to the specified IApplicationBuilder to enable authorization capabilities.
UseHttpRedirection 方法用于添加中间件,以便从 HTTP 重定向到 HTTPS。此外,我们还可以看到 UseAuthorization 方法,该方法将授权中间件添加到指定的 IApplicationBuilder 以启用授权功能。
Finally, we can see the MapControllers method that adds the endpoints from controller actions to the IEndpointRouteBuilder and the Run method that runs the application and block the calling thread until the host shutdown.
最后,我们可以看到 MapControllers 方法,该方法将控制器中的终结点添加到 IEndpointRouteBuilder,以及 Run 方法,该方法运行应用程序并阻止调用线程,直到主机关闭。
Microsoft advises that the order of adding different middlewares to the application builder is very important, and we are going to talk about that in the middleware section of this book.
Microsoft 建议,向应用程序构建器添加不同中间件的顺序非常重要,我们将在本书的中间件部分讨论这一点。
1.7 Environment-Based Settings
1.7 基于环境的设置
While we develop our application, we use the “development” environment. But as soon as we publish our application, it goes to the “production” environment. Development and production environments should have different URLs, ports, connection strings, passwords, and other sensitive information.
当我们开发应用程序时,我们使用 “development” 环境。但是,一旦我们发布应用程序,它就会进入 “production” 环境(生产环境)。开发和生产环境应具有不同的 URL、端口、连接字符串、密码和其他敏感信息。
Therefore, we need to have a separate configuration for each environment and that’s easy to accomplish by using .NET Core-provided mechanisms.
因此,我们需要为每个环境提供单独的配置,这可以通过使用 .NET Core 提供的机制轻松实现。
As soon as we create a project, we are going to see the appsettings.json file in the root, which is our main settings file, and when we expand it we are going to see the appsetings.Development.json file by default. These files are separate on the file system, but Visual Studio makes it obvious that they are connected somehow:
一旦我们创建了一个项目,我们将在根目录中看到 appsettings.json 文件,这是我们的主要设置文件,当我们展开它时,我们将看到 appsetings。Development.json 文件。这些文件在文件系统上是独立的,但 Visual Studio 清楚地表明它们以某种方式连接在一起:
The apsettings.{EnvironmentSuffix}.json files are used to override the main appsettings.json file. When we use a key-value pair from the original file, we override it. We can also define environment-specific values too.
apsettings.{EnvironmentSuffix}.json 文件用于覆盖主 appsettings.json 文件。当我们使用原始文件中的键值对时,我们会覆盖它。我们也可以定义特定于环境的值。
For the production environment, we should add another file: appsettings.Production.json:
对于生产环境,我们应该添加另一个文件:appsettings.Production.json:
The appsettings.Production.json file should contain the configuration for the production environment.
appsettings.Production.json文件应包含生产环境的配置。
To set which environment our application runs on, we need to set up the ASPNETCORE_ENVIRONMENT environment variable. For example, to run the application in production, we need to set it to the Production value on the machine we do the deployment to.
要设置应用程序在哪个环境上运行,我们需要设置 ASPNETCORE_ENVIRONMENT 环境变量。例如,要在生产环境中运行应用程序,我们需要在执行部署的机器上将其设置为 Production 值。
We can set the variable through the command prompt by typing set ASPNETCORE_ENVIRONMENT=Production in Windows or export ASPNET_CORE_ENVIRONMENT=Production in Linux.
我们可以通过命令提示符设置变量,方法是在 Windows 中键入 set ASPNETCORE_ENVIRONMENT=Production 或在 Linux 中键入 export ASPNET_CORE_ENVIRONMENT=Production。
ASP.NET Core applications use the value of that environment variable to decide which appsettings file to use accordingly. In this case, that will be appsettings.Production.json.
ASP.NET Core 应用程序使用该环境变量的值来决定相应地使用哪个 appsettings 文件。在本例中,这将是 appsettings.Production.json。
If we take a look at our launchSettings.json file, we are going to see that this variable is currently set to Development.
如果我们查看 launchSettings.json 文件,我们将看到此变量当前设置为 Development。
Now, let’s talk a bit more about the middleware in ASP.NET Core applications.
现在,让我们更多地讨论一下 ASP.NET Core 应用程序中的中间件。
1.8 ASP.NET Core Middleware
1.8 ASP.NET Core 中间件
As we already used some middleware code to modify the application’s pipeline (CORS, Authorization...), and we are going to use the middleware throughout the rest of the book, we should be more familiar with the ASP.NET Core middleware.
由于我们已经使用了一些中间件代码来修改应用程序的管道(CORS、Authorization...),并且我们将在本书的其余部分使用中间件,因此我们应该更熟悉 ASP.NET Core 中间件。
ASP.NET Core middleware is a piece of code integrated inside the application’s pipeline that we can use to handle requests and responses. When we talk about the ASP.NET Core middleware, we can think of it as a code section that executes with every request.
ASP.NET Core 中间件是一段集成在应用程序管道中的代码,我们可以使用它来处理请求和响应。当我们谈论 ASP.NET Core 中间件时,我们可以将其视为随每个请求一起执行的代码部分。
Usually, we have more than a single middleware component in our application. Each component can:
通常,我们的应用程序中有多个中间件组件。每个组件都可以:
• Pass the request to the next middleware component in the pipeline and also
• 将请求传递给管道中的下一个中间件组件,以及
• It can execute some work before and after the next component in the pipeline
• 它可以在管道中的下一个元件之前和之后执行一些工作
To build a pipeline, we are using request delegates, which handle each HTTP request. To configure request delegates, we use the Run, Map, and Use extension methods. Inside the request pipeline, an application executes each component in the same order they are placed in the code- top to bottom:
为了构建管道,我们使用请求委托来处理每个 HTTP 请求。要配置请求委托,我们使用 Run、Map 和 Use 扩展方法。在请求管道中,应用程序按照它们在代码中的放置顺序(从上到下)执行每个组件:
Additionally, we can see that each component can execute custom logic before using the next delegate to pass the execution to another component. The last middleware component doesn’t call the next delegate, which means that this component is short-circuiting the pipeline. This is a terminal middleware because it stops further middleware from processing the request. It executes the additional logic and then returns the execution to the previous middleware components.
此外,我们可以看到,在使用 next()将执行传递给另一个组件之前,每个组件都可以执行自定义 logic。最后一个中间件组件不调用下一个委托,这意味着该组件正在使管道短路。这是一个终端中间件,因为它会阻止进一步的中间件处理请求。它执行额外的 logic,然后将执行返回给前面的中间件组件。
Before we start with examples, it is quite important to know about the order in which we should register our middleware components. The order is important for the security, performance, and functionality of our applications:
在我们开始示例之前,了解我们应该注册中间件组件的顺序非常重要。该顺序对于我们应用程序的安全性、性能和功能非常重要:
As we can see, we should register the exception handler in the early stage of the pipeline flow so it could catch all the exceptions that can happen in the later stages of the pipeline. When we create a new ASP.NET Core app, many of the middleware components are already registered in the order from the diagram. We have to pay attention when registering additional existing components or the custom ones to fit this recommendation.
正如我们所看到的,我们应该在管道流的早期阶段注册异常处理程序,以便它可以捕获管道后期可能发生的所有异常。当我们创建新的 ASP.NET Core 应用程序时,许多中间件组件已经按照图中的顺序注册了。在注册其他现有组件或自定义组件以适应此建议时,我们必须注意。
For example, when adding CORS to the pipeline, the app in the development environment will work just fine if you don’t add it in this order. But we’ve received several questions from our readers stating that they face the CORS problem once they deploy the app. But once we suggested moving the CORS registration to the required place, the problem disappeared.
例如,在将 CORS 添加到管道时,如果不按此顺序添加,开发环境中的应用程序将正常工作。但是我们收到了读者的几个问题,他们指出,一旦部署了应用程序,他们就会面临 CORS 问题。但是,一旦我们建议将 CORS 注册移动到所需位置,问题就消失了。
Now, we can use some examples to see how we can manipulate the application’s pipeline. For this section’s purpose, we are going to create a separate application that will be dedicated only to this section of the book. The later sections will continue from the previous project, that we’ve already created.
现在,我们可以使用一些示例来了解如何作应用程序的管道。出于本节的目的,我们将创建一个单独的应用程序,该应用程序将专门用于本书的这一部分。后面的部分将从我们已经创建的上一个项目继续。
1.8.1 Creating a First Middleware Component
1.8.1 创建第一个 Middleware 组件
Let’s start by creating a new ASP.NET Core Web API project, and name it MiddlewareExample.
让我们首先创建一个新的 ASP.NET Core Web API 项目,并将其命名为 MiddlewareExample。
In the launchSettings.json file, we are going to add some changes regarding the launch profiles:
在 launchSettings.json 文件中,我们将添加一些有关启动配置文件的更改:
{
"profiles": {
"MiddlewareExample": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" }
}
}
}
Now, inside the Program class, right below the UseAuthorization part, we are going to use an anonymous method to create a first middleware component:
现在,在 Program 类中,在 UseAuthorization 部分的正下方,我们将使用匿名方法创建第一个中间件组件:
app.UseAuthorization();
app.Run(async context => { await context.Response.WriteAsync("Hello from the middleware component."); });
app.MapControllers();
We use the Run method, which adds a terminal component to the app pipeline. We can see we are not using the next delegate because the Run method is always terminal and terminates the pipeline. This method accepts a single parameter of the RequestDelegate type. If we inspect this delegate we are going to see that it accepts a single HttpContext parameter:
我们使用 Run 方法,该方法将终端组件添加到应用程序管道中。我们可以看到我们没有使用 next() 委托,因为 Run 方法始终是 terminal 并终止管道。此方法接受 RequestDelegate 类型的单个参数。如果我们检查这个委托,我们将看到它接受一个 HttpContext 参数:
namespace Microsoft.AspNetCore.Http {
public delegate Task RequestDelegate(HttpContext context);
}
So, we are using that context parameter to modify our requests and responses inside the middleware component. In this specific example, we are modifying the response by using the WriteAsync method. For this method, we need Microsoft.AspNetCore.Http namespace.
因此,我们使用该 context 参数来修改中间件组件内的请求和响应。在此特定示例中,我们将使用 WriteAsync 方法修改响应。对于此方法,我们需要 Microsoft.AspNetCore.Http 命名空间。
Let’s start the app, and inspect the result:
让我们启动应用程序,并检查结果:
There we go. We can see a result from our middleware.
好了。我们可以看到中间件的结果。
1.8.2 Working with the Use Method
1.8.2 使用 Use 方法
To chain multiple request delegates in our code, we can use the Use method. This method accepts a Func delegate as a parameter and returns a Task as a result:
要在代码中链接多个请求委托,我们可以使用 Use 方法。此方法接受 Func 委托作为参数,并返回 Task 作为结果:
public static IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware);
So, this means when we use it, we can make use of two parameters, context and next:
所以,这意味着当我们使用它时,我们可以使用两个参数,context 和 next:
app.UseAuthorization();
app.Use(async (context, next) =>
{
Console.WriteLine($"Logic before executing the next delegate in the Use method");
await next.Invoke();
Console.WriteLine($"Logic after executing the next delegate in the Use method");
});
app.Run(async context =>
{
Console.WriteLine($"Writing the response to the client in the Run method");
await context.Response.WriteAsync("Hello from the middleware component.");
});
app.MapControllers();
As you can see, we add several logging messages to be sure what the order of executions inside middleware components is. First, we write to a console window, then we invoke the next delegate passing the execution to another component in the pipeline. In the Run method, we write a second message to the console window and write a response to the client. After that, the execution is returned to the Use method and we write the third message (the one below the next delegate invocation) to the console window.
如你所见,我们添加了几个日志记录消息,以确保中间件组件内的执行顺序是什么。首先,我们写入控制台窗口,然后调用下一个委托,将执行传递给管道中的另一个组件。在 Run 方法中,我们将第二条消息写入控制台窗口,并将响应写入客户端。之后,执行将返回到 Use 方法,我们将第三条消息(下一个委托调用下面的消息)写入控制台窗口。
The Run method doesn’t accept the next delegate as a parameter, so without it to send the execution to another component, this component short-circuits the request pipeline.
Run 方法不接受下一个委托作为参数,因此如果没有它将执行发送到另一个组件,此组件会使请求管道短路。
Now, let’s start the app and inspect the result, which proves our execution order:
现在,让我们启动应用程序并检查结果,它证明了我们的执行顺序:
Maybe you will see two sets of messages but don’t worry, that’s because the browser sends two sets of requests, one for the /weatherforecast and another for the favicon.ico. If you, for example, use Postman to test this, you will see only one set of messages.
也许你会看到两组消息,但不要担心,那是因为浏览器发送了两组请求,一组用于 /weatherforecast,另一组用于 favicon.ico。例如,如果您使用 Postman 对此进行测试,您将只看到一组消息。
One more thing to mention. We shouldn’t call the next.Invoke after we send the response to the client. This can cause exceptions if we try to set the status code or modify the headers of the response.
还有一件事要提。我们不应该调用下一个。在将响应发送到客户端后调用。如果我们尝试设置状态代码或修改响应的标头,这可能会导致异常。
For example:
例如:
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Hello from the middleware component.");
await next.Invoke();
Console.WriteLine($"Logic after executing the next delegate in the Use method");
});
app.Run(async context =>
{
Console.WriteLine($"Writing the response to the client in the Run method");
context.Response.StatusCode = 200;
await context.Response.WriteAsync("Hello from the middleware component.");
});
Here we write a response to the client and then call next.Invoke. Of course, this passes the execution to the next component in the pipeline. There, we try to set the status code of the response and write another one. But let’s inspect the result:
在这里,我们向客户端写入响应,然后调用 next。调用。当然,这会将执行传递给管道中的下一个组件。在那里,我们尝试设置响应的状态代码并编写另一个状态代码。但让我们检查一下结果:
We can see the error message, which is pretty self-explanatory.
我们可以看到错误消息,这是不言自明的。
1.8.3 Using the Map and MapWhen Methods
1.8.3 使用 Map 和 MapWhen 方法
To branch the middleware pipeline, we can use both Map and MapWhen methods. The Map method is an extension method that accepts a path string as one of the parameters:
要对中间件管道进行分支,我们可以同时使用 Map 和 MapWhen 方法。该方法 Map 是一种扩展方法,它接受路径字符串作为参数之一:
public static IApplicationBuilder Map(this IApplicationBuilder app, PathString pathMatch, Action<IApplicationBuilder> configuration)
When we provide the pathMatch string, the Map method will compare it to the start of the request path. If they match, the app will execute the branch.
当我们提供 pathMatch 字符串时,Map 方法会将其与请求路径的开头进行比较。如果它们匹配,应用程序将执行分支。
So, let’s see how we can use this method by modifying the Program class:
那么,让我们看看如何通过修改 Program 类来使用此方法:
app.Use(async (context, next) =>
{
Console.WriteLine($"Logic before executing the next delegate in the Use method");
await next.Invoke();
Console.WriteLine($"Logic after executing the next delegate in the Use method");
});
app.Map("/usingmapbranch", builder =>
{
builder.Use(async (context, next) =>
{
Console.WriteLine("Map branch logic in the Use method before the next delegate");
await next.Invoke();
Console.WriteLine("Map branch logic in the Use method after the next delegate");
});
builder.Run(async context =>
{
Console.WriteLine($"Map branch response to the client in the Run method");
await context.Response.WriteAsync("Hello from the map branch.");
});
});
app.Run(async context =>
{
Console.WriteLine($"Writing the response to the client in the Run method");
await context.Response.WriteAsync("Hello from the middleware component.");
});
By using the Map method, we provide the path match, and then in the delegate, we use our well-known Use and Run methods to execute middleware components.
通过使用 Map 方法,我们提供路径匹配,然后在委托中,我们使用我们著名的 Use 和 Run 方法来执行中间件组件。
Now, if we start the app and navigate to /usingmapbranch, we are going to see the response in the browser:
现在,如果我们启动应用程序并导航到 /usingmapbranch,我们将在浏览器中看到响应:
But also, if we inspect console logs, we are going to see our new messages:
但是,如果我们检查控制台日志,我们将看到我们的新消息:
Here, we can see the messages from the Use method before the branch, and the messages from the Use and Run methods inside the Map branch. We are not seeing any message from the Run method outside the branch. It is important to know that any middleware component that we add after the Map method in the pipeline won’t be executed. This is true even if we don’t use the Run middleware inside the branch.
在这里,我们可以看到分支之前来自 Use 方法的消息,以及来自 Map 分支内的 Use 和 Run 方法的消息。我们没有看到来自分支外部的 Run 方法的任何消息。请务必知道,我们在管道中的 Map 方法之后添加的任何中间件组件都不会被执行。即使我们不在分支中使用 Run 中间件,也是如此。
1.8.4 Using MapWhen Method
1.8.4 使用 MapWhen 方法
If we inspect the MapWhen method, we are going to see that it accepts two parameters:
如果我们检查 MapWhen 方法,我们将看到它接受两个参数:
public static IApplicationBuilder MapWhen(this IApplicationBuilder app, Func<HttpContext, bool> predicate, Action<IApplicationBuilder> configuration)
This method uses the result of the given predicate to branch the request pipeline.
此方法使用给定谓词的结果对请求管道进行分支。
So, let’s see it in action:
那么,让我们看看它的实际效果:
app.Map("/usingmapbranch", builder =>
{
...
});
app.MapWhen(context => context.Request.Query.ContainsKey("testquerystring"), builder =>
{
builder.Run(async context =>
{
await context.Response.WriteAsync("Hello from the MapWhen branch.");
});
});
app.Run(async context =>
{
...
});
Here, if our request contains the provided query string, we execute the Run method by writing the response to the client. So, as we said, based on the predicate’s result the MapWhen method branch the request pipeline.
在这里,如果我们的请求包含提供的查询字符串,我们将通过将响应写入客户端来执行 Run 方法。因此,正如我们所说,根据谓词的结果,MapWhen 方法对请求管道进行分支。
Now, we can start the app and navigate to:
现在,我们可以启动应用程序并导航到:
https://localhost:5001?testquerystring=test
And there we go. We can see our expected message. Of course, we can chain multiple middleware components inside this method as well.
好了。我们可以看到预期的消息。当然,我们也可以在此方法中链接多个中间件组件。
So, now we have a good understanding of using middleware and its order of invocation in the ASP.NET Core application. This knowledge is going to be very useful to us once we start working on a custom error handling middleware (a few sections later).
因此,现在我们已经很好地了解了中间件的使用及其在 ASP.NET Core 应用程序中的调用顺序。一旦我们开始开发自定义错误处理中间件,这些知识将对我们非常有用(稍后将介绍几节)。
In the next chapter, we’ll learn how to configure a Logger service because it’s really important to have it configured as early in the project as possible. We can close this app, and continue with the CompanyEmployees app.
在下一章中,我们将学习如何配置 Logger 服务,因为在项目的早期配置它非常重要。我们可以关闭此应用程序,并继续使用 CompanyEmployees 应用程序。