ASP-NET-Core5-自定义-全-
ASP.NET Core5 自定义(全)
零、序言
ASP.NET Core 是 Microsoft 提供的功能最强大的 web 框架,它充满了隐藏的功能,使其更加强大和有用。
您的申请不应与框架相匹配;您的框架应该能够满足应用的实际需要。有了这本书,你将学习如何找到隐藏的螺丝钉,你可以转向获得最大的框架。
使用 ASP.NET Core 的开发人员将能够将他们的知识用于定制 ASP.NET Core 的实用指南。这本书提供了一种实际操作的实现方法及其相关的方法,可以让您立即启动并运行起来,提高效率。
这本书是您可能想要更改的默认 ASP.NET Core 行为以及如何更改的逐步说明的紧凑集合。
在本书的结尾,您将了解如何定制 ASP.NET Core,以便根据您的个人需求从中获得优化的应用。
ASP.NET Core 架构概述
在接下来的章节中,您应该熟悉 ASP.NET Core 的基本体系结构及其组件。这本书讨论了架构的几乎所有组件。
下图显示了 ASP.NET Core 5.0 的基本体系结构概述。让我们从底层到顶层快速浏览一下此处显示的组件:

底部是托管层。这一层引导 web 服务器以及启动 ASP.NET Core 应用所需的所有内容,包括日志记录、配置和服务提供商。该层创建实际的请求对象及其在上述层中使用的依赖项。
托管之上的下一层是中间件层。这一个与请求对象一起工作或操纵它。这会将中间件附加到请求对象。它执行中间件处理错误、验证 HST、CORS 等。
在这上面,有路由层,它根据定义的路由模式将请求路由到端点。Endpoint routing 是 ASP.NET Core 3.1 中的新播放器,它将路由与上面的 UI 层分离,以便为不同的端点(包括 Blazor、gRPC 和 SignalR)启用路由。提醒一下:在以前的 ASP.NET Core 版本中,路由是 MVC 层的一部分,其他每个 UI 层都需要实现自己的路由。
实际端点由第四层 UI 层提供,其中包含知名的 UI 框架Blazor、gRPC、Signal和MVC。作为 ASP.NET Core 开发人员,您将在这里完成大部分工作。
最后,在MVC上方,您将看到WebAPI和Razor 页面。
这本书涵盖了什么?
本书并未涵盖架构概述中提到的所有主题。本书涵盖了托管层的大部分主题,因为托管层包含了您可能需要自定义的大部分内容。这本书讨论了中间件和路由,以及 MVC 特性和一些更多的 WebAPI 主题,在这些主题中,您可以做一些魔术。
在每章的开头,我们将指出主题所属的级别。
哪些内容未涵盖,为什么?
本书不包括剃须刀页面、信号器、gRPC 和 Blazor。
原因是 gRPC 和 Signal 已经非常专业化,不需要定制。Blazor 是 ASP.NET Core 家族的新成员,目前尚未得到广泛应用。此外,作者对 Blazor 还不够熟悉,不知道定制 Blazor 的所有螺丝钉。Razor 页面位于 MVC 框架之上,MVC 的定制也适用于 Razor 页面。
这本书是给谁的
本书面向使用 ASP.NET Core 的 web 开发人员,他们可能需要更改默认行为才能完成任务。读者应该具备 ASP.NET Core 和 C#的基本知识,因为本书没有介绍这些技术的基础知识。读者还应该对 Visual Studio、Visual Studio 代码或任何其他支持 ASP.NET Core 和 C#的代码编辑器有很好的了解。
这本书涵盖的内容
第 1 章自定义日志,教您如何自定义日志行为以及如何添加自定义日志提供程序。
第二章定制应用配置帮助您了解如何使用不同的配置源,添加自定义配置提供者。
第三章定制依赖注入教您依赖注入(DI如何工作以及如何使用不同的 DI 容器。
第 4 章通过 Kestrel 配置和定制 HTTPS对 HTTPS 进行了不同的配置。
第 5 章使用 IHostedService 和 BackgroundService让您了解如何在后台执行任务。
第 6 章编写自定义中间件使用中间件处理 HTTP 上下文。
第 7 章内容协商使用自定义 OutputFormatter,教您如何基于 HTTP Accept 标头输出不同的内容类型。
第 8 章使用自定义模型绑定器管理输入,帮助您创建具有不同内容类型的输入模型。
第 9 章创建自定义 ActionFilter揭示了使用 ActionFilters 的面向方面编程。
第 10 章创建自定义 TagHelper,通过创建 TagHelper,可以简化 UI 层。
第 11 章配置 WebHostBuilder,帮助您了解如何在托管层上设置配置。
第 12 章使用不同的托管模式向您介绍不同平台上不同类型的托管。
第 13 章使用端点路由帮助您了解如何使用新路由提供自定义端点。
充分利用这本书
读者应具备 ASP.NET Core 和 C#的基本知识,以及 Visual Studio、Visual Studio 代码或任何其他支持 ASP.NET Core 和 C#的代码编辑器的基本知识。

您应该在计算机上安装最新的.NET 5 SDK。请在上查找最新版本 https://dotnet.microsoft.com/download/dotnet-core/ 。
您可以随意使用任何支持 ASP.NET Core 和 C#的代码编辑器。我们建议使用 Visual Studio 代码(https://code.visualstudio.com/ ),可在所有平台上使用,本书作者使用。
本书中的所有项目都将使用控制台、命令提示符、shell 或 PowerShell 创建。您可以随意使用任何您喜欢的控制台。作者使用托管在 cmder shell(中的 Windows 命令提示符 https://cmder.net/ )。我们不建议使用 VisualStudio 创建项目,因为基本配置可能会更改,并且 web 项目将在与本书中描述的不同的端口上启动。
您是否坚持使用.NETCore3.1?如果您由于任何原因无法在计算机上使用.NET 5,那么所有示例都可以使用.NET Core 3.1。当与.NET 5 存在差异时,本章将与.NET Core 3.1 进行比较,因为 3.1 版仍然是.NET Core 的当前长期支持版本。
如果您使用的是本书的数字版本,我们建议您自己键入代码或通过 GitHub 存储库访问代码(下一节提供链接)。这样做将帮助您避免与复制和粘贴代码相关的任何潜在错误。
下载示例代码文件
您可以从您的账户www.packt.com下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packtpub.com/support并注册,将文件通过电子邮件直接发送给您。
您可以通过以下步骤下载代码文件:
- 登录或注册www.packt.com。
- 选择支持选项卡。
- 点击代码下载。
- 在搜索框中输入图书名称,然后按照屏幕上的说明进行操作。
下载文件后,请确保使用以下最新版本解压或解压缩文件夹:
- WinRAR/7-Zip for Windows
- 适用于 Mac 的 Zipeg/iZip/UnRarX
- 适用于 Linux 的 7-Zip/PeaZip
该书的代码包也托管在 GitHub 上的https://github.com/PacktPublishing/Customizing-ASP.NET-Core-5.0 。如果代码有更新,它将在现有 GitHub 存储库中更新。
我们的丰富书籍和视频目录中还有其他代码包,请访问https://github.com/PacktPublishing/ 。看看他们!
使用的约定
本书中使用了许多文本约定。
Code in text:表示文本中的码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。下面是一个示例:“打开您刚刚创建的项目的文件Startup.cs
代码块设置如下:
public Task StopAsync(CancellationToken cancellationToken)
{
logger.LogInformation(“Hosted service stopping”);
return Task.CompletedTask;
}
当我们希望提请您注意代码块的特定部分时,相关行或项目以粗体显示:
app.UseEndpoints(endpoints =>
{
endpoints.MapGet(“/”, async context =>
{
await context.Response.WriteAsync(
“Hello World!”);
});
endpoints.MapAppStatus(“/status”, “Status”);
});
任何命令行输入或输出的编写方式如下:
dotnet new web -n SampleProject -o SampleProject
cd SampleProject
code .
粗体:表示一个新术语、一个重要单词或您在屏幕上看到的单词。例如,菜单或对话框中的单词出现在文本中,如下所示。下面是一个示例:“从管理面板中选择系统信息
提示或重要提示
看起来像这样。
联系
我们欢迎读者的反馈。
一般反馈:如果您对本书的任何方面有疑问,请在邮件主题中注明书名,并发送电子邮件至customercare@packtpub.com。
勘误表:尽管我们已尽一切努力确保内容的准确性,但还是会出现错误。如果您在本书中发现错误,如果您能向我们报告,我们将不胜感激。请访问www.packtpub.com/support/errata,选择您的书籍,单击 errata 提交表单链接,然后输入详细信息。
盗版:如果您在互联网上发现我们作品的任何形式的非法复制品,请您提供我们的位置地址或网站名称,我们将不胜感激。请致电与我们联系 copyright@packt.com带有指向该材料的链接。
如果您有兴趣成为一名作家:如果您对某个主题有专业知识,并且您有兴趣撰写或贡献一本书,请访问authors.packtpub.com。
审查
请留下评论。一旦你阅读并使用了这本书,为什么不在你购买它的网站上留下评论呢?然后,潜在读者可以看到并使用您的无偏见意见做出购买决定,我们 Packt 可以了解您对我们产品的看法,我们的作者可以看到您对他们书籍的反馈。非常感谢。
有关 Packt 的更多信息,请访问Packt.com。
一、自定义日志
在本书关于自定义 ASP.NET Core 的第一章中,您将看到如何自定义日志记录。默认日志记录仅写入控制台或调试窗口。这在大多数情况下都很好,但可能需要登录到接收器,例如文件或数据库。也许您希望使用其他信息扩展日志记录程序。在这些情况下,您需要知道如何更改默认日志记录。
在本章中,我们将介绍以下主题:
- 配置日志记录
- 创建自定义记录器
- 插入现有的第三方记录器提供商
本章中的主题涉及 ASP.NET Core 体系结构的托管层:

图 1.1–ASP.NET Core 体系结构
技术要求
要遵循本章中的描述,您需要创建一个 ASP.NET Core MVC 应用。打开控制台、shell 或 bash 终端,并切换到工作目录。使用以下命令创建新的 MVC 应用:
dotnet new mvc -n LoggingSample -o LoggingSample
现在,在 Visual Studio 中双击项目文件或在 VS 代码中,在已打开的控制台中键入以下命令来打开项目:
cd LoggingSample
code.
本章中的所有代码示例都可以在本书的 GitHub 存储库中的中找到 https://github.com/PacktPublishing/Customizing-ASP.NET-Core-5.0/tree/main/Chapter01 。
C 配置日志
在以前版本的 ASP.NET Core(2.0 之前)中,Startup.cs中配置了日志记录。提醒一下,从 2.0 版开始,Startup.cs文件被简化,很多配置被移动到默认的WebHostBuilder,在Program.cs中被调用。此外,日志记录被移动到默认值WebHostBuilder。
在 ASP.NET Core 3.1 及更高版本中,Program.cs文件变得更通用,将首先创建IHostBuilder。IHostBuilder对于在没有所有 ASP.NET web 内容的情况下引导应用非常有用。在接下来的章节中,我们将了解更多关于IHostBuilder的内容。有了这个IHostBuilder,我们创建了IWebHostBuilder来配置 ASP.NET Core。在 ASP.NET Core 3.1 及更高版本中,我们使用webBuilder变量获得IWebHostBuilder:
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>();
});
}
在 ASP.NET Core 中,您可以覆盖和自定义几乎所有内容。这包括日志记录。IWebHostBuilder有很多扩展方法覆盖默认行为。要覆盖日志记录的默认设置,我们需要选择ConfigureLogging方法。下面的代码片段显示的日志记录与ConfigureWebHostDefaults()方法中配置的日志记录几乎完全相同:
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.ConfigureLogging((hostingContext, logging) =>
{
logging.AddConfiguration(
hostingContext.Configuration.GetSection
("Logging"));
logging.AddConsole();
logging.AddDebug();
})
.UseStartup<Startup>();
});
这个方法需要一个 lambda 来获取WebHostBuilderContext和LoggingBuilder来配置日志记录。
现在我们已经了解了如何配置日志记录,让我们看看如何构建自定义日志记录程序。
创建自定义记录器
为了演示一个自定义记录器,让我们使用我创建的一个简单的小记录器,它能够在控制台中使用特定的日志级别对日志条目进行着色。这称为ColoredConsoleLogger,将使用LoggerProvider添加和创建,我们也需要自己编写。要指定要着色的颜色和日志级别,我们需要添加一个配置类。
在接下来的片段中,所有三个部分(Logger、LoggerProvider和Configuration都显示出来:
-
让我们创建记录器的配置类。我们称之为
ColoredConsoleLoggerConfiguration。此类包含三个要定义的属性—LogLevel、EventId和Color,可设置为:public class ColoredConsoleLoggerConfiguration { public LogLevel LogLevel { get; set; } = LogLevel.Warning; public int EventId { get; set; } = 0; public ConsoleColor Color { get; set; } = ConsoleColor.Yellow; } -
接下来,我们需要一个提供者来检索配置并创建实际的记录器实例:
public class ColoredConsoleLoggerProvider : IloggerProvider { private readonly ColoredConsoleLoggerConfiguration _config; private readonly ConcurrentDictionary<string, ColoredConsoleLogger> _loggers = new ConcurrentDictionary<string, ColoredConsoleLogger>(); public ColoredConsoleLoggerProvider (ColoredConsoleLoggerConfiguration config) { _config = config; } public ILogger CreateLogger(string categoryName) { return _loggers.GetOrAdd(categoryName, name => new ColoredConsoleLogger(name, _config)); } public void Dispose() { _loggers.Clear(); } } -
The third class is the actual logger we want to use:
public class ColoredConsoleLogger : Ilogger { private static object _lock = new Object(); private readonly string _name; private readonly ColoredConsoleLoggerConfiguration _config; public ColoredConsoleLogger( string name, ColoredConsoleLoggerConfiguration config) { _name = name; _config = config; } public IDisposable BeginScope<TState>(TState state) { return null; } public bool IsEnabled(LogLevel logLevel) { return logLevel == _config.LogLevel; } public void Log<TState>( LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter) { if (!IsEnabled(logLevel)) { return; } lock (_lock) { if (_config.EventId == 0 || _config.EventId == eventId.Id) { var color = Console.ForegroundColor; Console.ForegroundColor = _config.Color; Console.WriteLine($"{logLevel.ToString()} - {eventId.Id} - {_name} - {formatter(state, exception)}"); Console.ForegroundColor = color; } } } }我们现在需要锁定实际的控制台输出,因为我们会遇到一些竞争条件,不正确的日志条目会用错误的颜色着色,因为控制台本身并不是真正的线程安全的。
-
如果这样做了,我们可以开始将新的记录器插入到
Program.cs文件logging.ClearProviders(); var config = new ColoredConsoleLoggerConfiguration { LogLevel = LogLevel.Information, Color = ConsoleColor.Red }; logging.AddProvider(new ColoredConsoleLoggerProvider(config));中的配置中
如果不想使用现有的记录器,可以清除以前添加的所有记录器提供程序。然后,我们调用AddProvider添加一个具有特定设置的ColoredConsoleLoggerProvider类的新实例。我们还可以添加更多具有不同设置的提供者实例。
这显示了您如何以不同的方式处理不同的日志级别。您可以使用它发送有关硬错误的电子邮件,将调试消息记录到与常规信息消息不同的日志接收器,等等:

图 1.2–自定义记录器的屏幕截图
图 1.2显示了先前创建的自定义记录器的彩色输出。
在许多情况下,编写自定义记录器是没有意义的,因为已经有许多优秀的第三方记录器可用,例如ELMAH、log4net和NLog。在下一节中,我们将看到如何在 ASP.NET 核心中使用NLog。
插入现有的第三方记录器提供商
NLog是最早作为.NET 标准库提供并可在 ASP.NET Core 中使用的记录器之一。NLog还提供了一个记录器提供程序,可以方便地插入 ASP.NET Core。
您将通过 NuGet(找到NLoghttps://www.nuget.org/packages/NLog.Web.AspNetCore 和 GitHub(上)https://github.com/NLog/NLog.Web )。即使NLog尚未明确适用于 ASP.NET Core 5.0,它仍将适用于此版本:
-
我们需要添加一个
NLog.Config文件,该文件定义了两个不同的接收器,将所有消息记录在一个日志文件中,而自定义消息只记录在另一个文件中。由于此文件太长,无法打印,您可以查看或直接从 GitHub 下载:https://github.com/PacktPublishing/Customizing-ASP.NET-Core-5.0/blob/main/Chapter01/LoggingSample5.0/NLog.Config 。 -
We then need to add the
NLogASP.NET Core package from NuGet:dotnet add package NLog.Web.AspNetCore重要提示
在执行该命令之前,请确保您位于项目目录中!
-
Now, you only need to clear all the other providers in the
ConfigureLoggingmethod inProgram.csand to useNLogwith theIWebHostBuilderusing theUseNLog()method:Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder .ConfigureLogging((hostingContext, logging) => { logging.ClearProviders(); logging.SetMinimumLevel(LogLevel.Trace); }) .UseNLog() .UseStartup<Startup>(); });在这里,您可以根据需要添加任意多的记录器提供程序。
这包括使用现有的记录器。现在让我们回顾一下我们在这些初始 al 页面中所涵盖的内容。
总结
隐藏基本配置的好处在于,它允许您清理新搭建的项目,并使实际启动尽可能简单。开发人员能够专注于实际功能。然而,应用增长越多,日志记录就变得越重要。默认的日志记录配置很简单,工作起来很有魅力,但在生产中,您需要一个持久化的日志来查看过去的错误。因此,您需要添加自定义日志或更灵活的日志,如NLog或log4net。
在下一章中,您将了解有关如何配置 ASP.NET Core 的更多信息。
二、自定义 App 配置
第二章介绍应用配置,如何使用它,以及如何自定义 ASP.NET 配置以采用不同的方式配置应用。也许您已经有了一个现有的 XML 配置,或者希望通过不同类型的应用共享一个 YAML 配置文件。有时,从数据库中读取配置值也是有意义的。
在本章中,我们将介绍以下主题:
- 配置配置
- 使用类型化配置
- 使用 INI 文件进行配置
- 配置提供程序
本章主题涉及 ASP.NET Core 架构的托管层:

图 2.1–ASP.NET Core 体系结构
T 技术要求
要遵循本章中的描述,您需要创建一个 ASP.NET Core MVC 应用。打开控制台、shell 或 bash 终端,并切换到工作目录。使用以下命令创建新的 MVC 应用:
dotnet new mvc -n ConfigureSample -o ConfigureSample
现在,在 Visual Studio 中双击项目文件或在 VS 代码中,在已打开的控制台中键入以下命令来打开项目:
cd ConfigureSample
code.
本章中的所有代码示例都可以在本书的 GitHub 存储库中的中找到 https://github.com/PacktPublishing/Customizing-ASP.NET-Core-5.0/tree/main/Chapter02 。
配置配置
让我们先看看如何配置您的各种配置选项。
从 ASP.NET Core 2.0 开始,该配置隐藏在默认配置WebHostBuilder中,不再是Startup.cs的一部分。这有助于保持启动的干净和简单。
在 ASP.NET Core 3.1 和 ASP.NET Core 5.0 中,代码如下所示:
// ASP.NET Core 3.0 and later
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
}
}
幸运的是,您还可以覆盖默认设置,以您需要的方式自定义配置。
当您创建一个新的 ASP.NET Core 项目时,您已经配置了appsettings.json和appsettings.Development.json。您可以也应该使用这些配置文件来配置您的应用;这是预先配置的方式,大多数 ASP.NET Core 开发人员都会寻找一个appsettings.json文件来配置应用。这是绝对好的,效果相当好。
下面的代码片段显示了用于读取appsettings.json文件的封装默认配置:
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder
.ConfigureAppConfiguration((builderContext,
config) =>
{
var env = builderContext.HostingEnvironment;
config.SetBasePath(env.ContentRootPath);
config.AddJsonFile(
"appsettings.json",
optional: false,
reloadOnChange: true);
config.AddJsonFile(
$"appsettings.{env.EnvironmentName}.json",
optional: true,
reloadOnChange: true);
config.AddEnvironmentVariables();
})
.UseStartup<Startup>();
});
此配置还设置应用的基本路径,并通过环境变量添加配置。ConfigureAppConfiguration方法接受传入WebHostBuilderContext和ConfigurationBuilder的 lambda 方法。
无论何时定制应用配置,都应使用AddEnvironmentVariables()方法通过环境变量添加配置,作为最后一步。配置的顺序很重要,稍后添加的配置提供程序将覆盖先前添加的配置。确保环境变量始终覆盖通过文件设置的配置。这样,您还可以确保 Azure 应用服务上应用的配置将作为环境变量传递给应用。
IConfigurationBuilder有很多扩展方法来添加更多配置,比如 XML 或 INI 配置文件,以及内存中的配置。您可以找到由社区构建的用于读取 YAML 文件、数据库值等的其他配置提供程序。在下一节中,我们将看到如何读取 INI 文件。首先,我们将介绍如何使用类型化配置。
使用类型化配置
在尝试读取 INI 文件之前,您应该了解如何使用键入的配置,而不是通过IConfiguration逐键读取配置。
要读取类型化配置,需要定义要配置的类型。我通常创建一个名为AppSettings的类,如下所示:
public class AppSettings
{
public int Foo { get; set; }
public string Bar { get; set; }
}
这是一个简单的 POCO 类,只包含应用设置值。这些类可以在Startup.cs中的ConfigureServices方法中填入特定的配置部分:
services.Configure<AppSettings>
(Configuration.GetSection("AppSettings"));
这样,类型化配置也可以在依赖项注入容器中注册为服务,并且可以在应用中的任何地方使用。您可以为每个配置部分创建不同的配置类型。在大多数情况下,一个部分应该可以,但有时将设置划分为不同的部分是有意义的。下一个代码段显示了如何在 MVC 控制器中使用配置:
public class HomeController : Controller
{
private readonly AppSettings _options;
public HomeController(IOptions<AppSettings> options)
{
_options = options.Value;
}
IOptions<AppSettings>是对AppSettings类型的包装,Value属性包含AppSettings的实际实例,包括配置文件中的值。
要尝试此操作,appsettings.json文件需要配置AppSettings部分,否则值为空或未设置。现在我们将该部分添加到appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*",
"AppSettings": {
"Foo": 123,
"Bar": "Bar"
}
}
接下来,我们将研究如何将 INI 文件用于上的配置。
使用 INI 文件进行配置
要同时使用 INI 文件来配置应用,您需要在Program.cs中的ConfigureAppConfiguration()方法中添加 INI 配置:
config.AddIniFile(
"appsettings.ini",
optional: false,
reloadOnChange: true);
config.AddJsonFile(
$"appsettings.{env.EnvironmentName}.ini",
optional: true,
reloadOnChange: true);
此代码以与 JSON 配置文件相同的方式加载 INI 文件。第一行是必需的配置,第二行是可选配置,具体取决于当前运行时环境。
INI 文件可能如下所示:
[AppSettings]
Bar="FooBar"
如您所见,该文件包含一个名为AppSettings的节和一个名为Bar的属性。
早些时候,我们说过配置的顺序很重要。如果在通过 JSON 文件进行配置之后添加两行以通过 INI 文件进行配置,INI 文件将覆盖 JSON 文件中的设置。Bar属性被"FooBar"覆盖,而Foo属性保持不变,因为它不会被覆盖。此外,INI 文件中的值将通过先前创建的类型化配置提供。
其他所有配置提供程序的工作方式都相同。现在让我们看看配置提供程序的外观。
配置提供程序
配置提供者是由配置源创建的IConfigurationProvider的实现,是IConfigurationSource的实现。然后,配置提供程序从某处读取数据,并通过Dictionary提供数据。
要向 ASP.NET Core 添加自定义或第三方配置提供程序,您需要调用ConfigurationBuilder上的Add方法并插入配置源。这只是一个例子:
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureAppConfiguration((builderContext,
config) =>
{
var env = builderContext.HostingEnvironment;
config.SetBasePath(env.ContentRootPath);
config.AddJsonFile(
"appsettings.json",
optional: false,
reloadOnChange: true);
config.AddJsonFile(
$"appsettings.{env.EnvironmentName}.json",
optional: true,
reloadOnChange: true);
// add new configuration source
config.Add(new MyCustomConfigurationSource
{
SourceConfig = //configure whatever source
Optional = false,
ReloadOnChange = true
});
config.AddEnvironmentVariables();
})
.UseStartup<Startup>();
});
通常,您会创建一个扩展方法来更轻松地添加配置源:
config.AddMyCustomSource("source", optional: false,
reloadOnChange: true);
Andrew Lock 编写了一个关于如何创建自定义配置提供程序的非常详细的具体示例。您可以在本章的进一步阅读部分找到这一点。
总结
在大多数情况下,您不需要添加其他配置提供程序或创建自己的配置提供程序,但最好知道如何更改它,以防万一。另外,使用类型化配置是读取和提供设置的好方法。在经典的 ASP.NET 中,我们使用手动创建的 façade 以键入的方式读取应用设置。现在,只需提供一个类型就可以自动完成。该类型将通过依赖项注入自动实例化、填充和提供。
要了解有关在 ASP.NET Core 5.0 中自定义依赖项注入的更多信息,请看下一章。
进一步的 r 读数
Andrew Lock,在 ASP.NET Core中创建自定义配置提供程序:https://andrewlock.net/creating-a-custom-iconfigurationprovider-in-asp-net-core-to-parse-yaml/ 。
三、自定义依赖注入
在第三章中,我们将了解 ASP.NET Core依赖项注入以及如何定制它以使用不同的依赖项注入容器(如果需要)。
在本章中,我们将介绍以下主题:
- 使用不同的依赖项注入容器
- 看看
ConfigureServices方法 - 使用不同的
ServiceProvider类型 - 介绍 Scrutor
本章中的主题涉及 ASP.NET Core 体系结构的托管层:

图 3.1–ASP.NET Core 体系结构
技术要求
要遵循本章中的描述,您需要创建一个 ASP.NET Core MVC 应用。打开控制台、shell 或 bash 终端,并切换到工作目录。使用以下命令创建新的 MVC 应用:
dotnet new mvc -n DiSample -o DiSample
现在,在 Visual Studio 中双击项目文件或在 VS 代码中,在已打开的控制台中键入以下命令来打开项目:
cd DiSample
code .
本章中的所有代码示例都可以在本书的 GitHub 存储库中的中找到 https://github.com/PacktPublishing/Customizing-ASP.NET-Core-5.0/tree/main/Chapter03 。
U 使用不同的依赖注入容器
在大多数项目中,您实际上不需要使用不同的依赖注入(DI容器。ASP.NET Core 中现有的 DI 实现支持主要的基本功能,并且能够高效快速地工作。但是,其他一些 DI 容器支持一些您可能希望在应用中使用的有趣功能:
- Ninject允许您创建一个支持模块作为轻量级依赖项的应用;例如,您可能希望将模块放入特定目录,并在应用中自动注册它们。
- 您可能希望在应用外部的配置文件中,在 XML 或 JSON 文件中,而不是仅在 C#中配置服务。这是各种 DI 容器中的一个常见功能,但 ASP.NET Core 尚不支持。
- 也许您不想拥有一个不可变的 DI 容器,因为您想在运行时添加服务。这也是一些 DI 容器中的一个常见特性。
现在让我们看看ConfigureServices方法如何让您能够使用替代 DI 容器。
查看 ConfigureServices 方法
让我们将当前的方法与以前的长期支持版本进行比较,看看有什么变化。如果要使用版本 3.1 创建新的 ASP.NET Core 项目并打开Startup.cs,您将找到配置服务的方法,如下所示:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user
// consent for non-essential cookies is
// needed for a given request.
options.CheckConsentNeeded = context => true;
});
services.AddControllersWithViews();
services.AddRazorPages();
}
相比之下,在 ASP.NET Core 5.0 中,它看起来是这样的:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
}
在这两种情况下,该方法都会得到IServiceCollection,其中已经填充了 ASP.NET Core 所需的一系列服务。此服务是由托管服务和 ASP.NET Core 中在调用ConfigureServices方法之前执行的部分添加的。
在方法内部,添加了更多的服务。首先,将包含 cookie 策略选项的配置类添加到ServiceCollection。在下面的示例中,您还可以看到一个名为MyService的定制服务,它实现了IService接口。之后,AddMvc()方法添加了 MVC 框架所需的另一组服务。到目前为止,我们在IServiceCollection注册了大约 140 项服务。但是,服务集合不是实际的 DI 容器。
实际的 DI 容器被包装在所谓的服务提供者中,该服务提供者将在服务集合的中创建。IServiceCollection注册了一个扩展方法来创建IServiceProvider出服务集合,您可以在下面的代码片段中看到:
IServiceProvider provider = services.BuildServiceProvider()
ServiceProvider包含运行时无法更改的不可变容器。使用默认的ConfigureServices方法,IServiceProvider在调用此方法后在后台创建。
接下来,我们将了解更多关于在 DI 定制过程 ess 中应用替代ServiceProvider类型的信息。
使用不同的服务提供商类型
如果另一个容器已经支持 ASP.NET Core,则更改为其他容器或自定义 DI 容器相对容易。通常情况下,另一个容器将使用IServiceCollection来喂入自己的容器。第三方 DI 容器通过在集合上循环,将已注册的服务移动到另一个容器:
-
Let's start by using
Autofacas a third-party container. Type the following command into your command line to load the NuGet package:dotnet add package Autofac.Extensions.DependencyInjectionAutofac对这有好处,因为你很容易就能看到这里发生了什么。 -
To register a custom IoC container, you need to register a different
IServiceProviderFactoryinterface. In that case, you'll want to useAutofacServiceProviderFactoryif you useAutofac.IserviceProviderFactorywill create aServiceProviderinstance. The third-party container should provide one, if it supports ASP.NET Core.您应该将此小扩展方法放置在
Program.cs中,以便向IHostBuilder注册AutofacServiceProviderFactory:public static class IHostBuilderExtension { public static IHostBuilder UseAutofacServiceProviderFactory( this IHostBuilder hostbuilder) { hostbuilder.UseServiceProviderFactory <ContainerBuilder>( new AutofacServiceProviderFactory()); return hostbuilder; } } -
要使用此扩展方法,您可以稍微更改
CreateHostBuilder方法:public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .UseAutofacServiceProviderFactory() .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
这会将AutofacServiceProviderFactory功能添加到IHostBuilder并启用AutofacIoC 容器。如果你有这个,你可以随意使用Autofac。在Startup.cs中,您可以使用IServiceCollection注册依赖项,或者添加一个名为ConfigureContainer的附加方法,以 Autofac 方式进行注册。
向 ConfigureContainer 注册依赖项
ConfigureContainer是您可以直接向Autofac注册物品的地方。这是在ConfigureServices方法之后运行的,因此这里的内容将覆盖ConfigureServices中的注册:
public void ConfigureContainer(ContainerBuilder builder)
{
builder.RegisterType<MyService>().As<IService>();
// custom service
}
自定义MyService服务现在使用 Autofac 方式注册。在下一节中,我们将介绍一个名为 Scrutor 的有用包。
介绍 Scrutor
您并不总是需要替换现有的.NET Core DI 容器来获得和使用一些很酷的功能。在本章的开头,我提到了服务的自动注册,这可以通过其他 DI 容器完成。这也可以通过一个名为Scrutor(的漂亮 NuGet 包来实现 https://github.com/khellang/Scrutor 由克里斯蒂安·赫朗(撰写 https://kristian.hellang.com 。Scrutor 扩展了IServiceCollection以自动向.NET Core DI 容器注册服务。
笔记
安德鲁·洛克发表了一篇关于 Scrutor 的非常详细的博客文章。与其重复他说的话,我建议你继续阅读这篇文章,了解更多信息:使用 Scrutor 自动向 ASP.NET Core DI 容器注册你的服务,该容器位于上 https://andrewlock.net/using-scrutor-to-automatically-register-your-services-with-the-asp-net-core-di-container/ 。
总结
使用我们在本章中演示的方法,您将能够使用任何与.NET 标准兼容的 DI 容器来替换现有容器。如果您选择的容器不包含ServiceProvider,请创建自己的实现IServiceProvider并在其中使用 DI 容器的容器。如果您选择的容器没有提供填充容器中已注册服务的方法,请创建您自己的方法。循环已注册的服务并将它们添加到另一个容器中。
实际上,最后一步听起来很简单,但可能是一项艰巨的任务,因为您需要将所有可能的IServiceCollection注册转换为不同容器的注册。该任务的复杂性取决于另一个 DI 容器的实现细节。
无论如何,您可以选择使用任何与.NET 标准兼容的 DI 容器。您可以更改 ASP.NET Core 中的许多默认实现。
这也是您可以在 Windows 上使用默认 HTTPS 行为所做的事情,我们将在下一章中了解更多。
四、通过 Kestrel 配置和定制 HTTPS
HTTPS默认为开启状态,是一级功能。在 Windows 上,启用 HTTPS 所需的证书从 Windows 证书存储加载。如果在 Linux 或 Mac 上创建项目,则将从证书文件加载证书。
即使您想创建一个项目在 IIS 或 NGINX web 服务器后面运行它,HTTPS 也是启用的。通常,在这种情况下,您将在 IIS 或 NGINX web 服务器上管理证书。但是,这应该不是问题,所以不要在 ASP.NET Core 设置中禁用 HTTPS。
如果您在防火墙后运行无法从 internet 访问的服务,则直接在 ASP.NET Core 应用中管理证书是有意义的;服务,如基于微服务的应用的后台服务,或自托管 ASP.NET Core 应用中的服务。
在某些情况下,也可以从 Windows 上的文件加载证书。这可能是在您将在 Docker for Windows 或 Linux 上运行的应用中。就个人而言,我喜欢从文件加载证书的灵活方式。
本章仅涉及两个主题:
- 介绍 Kestrel
- 设置 Kestrel
本章中的主题涉及 ASP.NET Core 体系结构的主机层:

图 4.1–ASP.NET Core 体系结构
技术要求
要遵循本章中的描述,您需要创建一个 ASP.NET Core MVC 应用。打开控制台、shell 或 bash 终端,并切换到工作目录。使用以下命令创建新的 MVC 应用:
dotnet new mvc -n HttpSample -o HttpSample
现在,在 Visual Studio 中双击项目文件或在 VS 代码中,在已打开的控制台中键入以下命令来打开项目:
cd HttpSample
code .
本章中的所有代码示例都可以在本书的 GitHub 存储库中的中找到 https://github.com/PacktPublishing/Customizing-ASP.NET-Core-5.0/tree/main/Chapter04 。
介绍 Kestrel
Kestrel是一个新实现的 HTTP 服务器,是 ASP.NET Core 的托管引擎。每个 ASP.NET Core 应用都将在 Kestrel 服务器上运行。经典的 ASP.NET 应用(在.NET Framework 上运行)通常直接在 IIS web 服务器上运行。有了 ASP.NET Core,微软的灵感来源于Node.js,它还提供了一个名为Libuv的 HTTP 服务器。在 ASP.NET Core 的第一个版本中,微软还使用了 Libuv,然后在其顶部添加了一层,称为 Kestrel。Node.js 和 ASP.NET Core 当时共享同一个 HTTP 服务器。
由于.NET Core 框架的发展,以及.NET 套接字在其上的实现,微软已经基于.NET 套接字构建了自己的 HTTP 服务器,并删除了 Libuv,这是一个他们不拥有或控制的依赖项。现在,Kestrel 是一个功能齐全的 HTTP 服务器,运行 ASP.NET Core 应用。
IIS web 服务器充当反向代理,将流量转发给 Kestrel 并管理 Kestrel 进程。在 Linux 上,通常使用 NGINX 作为 Kestrel 的反向代理。
设置 Kestrel
正如我们在本书的前两章所做的那样,我们需要稍微重写默认的WebHostBuilder类型来设置 Kestrel。使用 ASP.NET Core 3.0 及更高版本,可以将默认的 Kestrel 基本配置替换为自定义配置。这意味着 Kestrel web 服务器配置为主机生成器:
-
You will be able to add and configure Kestrel manually simply by using it. The following code shows what happens when you call the
UseKestrel()method onIwebHostBuilder. Let's now see how this fits into theCreateWebHostBuildermethod:public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder .UseKestrel(options => { }) .UseStartup<Startup>(); } }UseKestrel()方法接受配置 Kestrel web 服务器的操作。 -
What we actually need to do is to configure the addresses and ports that the web server is listening on. For the HTTPS port, we also need to configure how the certificate should be loaded:
.UseKestrel(options => { options.Listen(IPAddress.Loopback, 5000); options.Listen(IPAddress.Loopback, 5001, listenOptions => { listenOptions.UseHttps("certificate.pfx", "topsecret"); }); })在这个片段中,我们添加了要侦听的地址和端口。第二个定义为配置为使用 HTTPS 的安全端点。
UseHttps()方法被多次重载,以从 Windows 证书存储以及文件中加载证书。在本例中,我们将使用项目文件夹中名为certificate.pfx的文件。 -
要创建一个证书文件来处理此配置,请打开证书存储并导出 Visual Studio 创建的开发证书。它位于当前用户证书下的个人证书:

图 4.2–证书
右键单击此条目。在关联菜单中,进入所有任务并点击导出。在证书导出向导中,选择是,导出私钥并在下一屏幕中选择.PFX格式。点击下一步。在这里,您需要设置密码。这与您在代码中需要使用的密码完全相同,如示例中所示。选择文件名和存储文件的位置,然后按下一步。最后一个屏幕将显示摘要。按完成将证书保存到文件中。
为了您的安全
仅使用以下行来处理此配置:
listenOptions.UseHttps("certificate.pfx", "topsecret");
问题在于硬编码密码。永远不要在推送到任何源代码存储库的代码文件中存储密码。确保从 ASP.NET Core 的配置 API 加载密码。在本地开发计算机上使用用户机密,并在服务器上使用环境变量。在 Azure 上,使用应用设置存储密码。如果密码被标记为 pass 单词,则密码将隐藏在 Azure 门户 UI 上。
总结
这只是一个小的定制,但是如果您想在不同的平台之间共享代码,或者希望在 Docker 上运行应用,并且不想担心证书存储等等,那么它应该会有所帮助。
通常,如果您在 IIS 或 NGINX 等 web 服务器后面运行应用,则不需要关心 ASP.NET Core 5.0 应用中的证书。但是,如果您将应用托管在另一个应用中,无论是在 Docker 上还是在没有 IIS 或 NGINX 的情况下,您都需要。
ASP.NET Core 5.0 具有在应用内部后台运行任务的新功能。本主题将在下一章中介绍。*
五、使用IHostedService和BackgroundService
第五章不是关于定制的;它更多的是关于一个特性,您可以使用它来创建后台服务,以便在应用内异步运行任务。我使用此功能定期从小型 ASP.NET Core 应用中的远程服务获取数据。
我们将研究以下主题:
- 介绍
IHostedService - 介绍
BackgroundService - 实施新的工人服务项目
本章的主题涉及 ASP.NET Core 体系结构的主机层:

图 5.1–ASP.NET Core 体系结构
技术要求
T 要按照本章中的说明进行操作,您需要创建一个 ASP.NET Core 应用。打开控制台、shell 或 bash 终端,并切换到工作目录。使用以下命令创建新的 MVC 应用:
dotnet new mvc -n HostedServiceSample -o HostedServiceSample
现在,通过双击项目文件或在 VS 代码中将文件夹更改为项目并在已打开的控制台中键入以下命令,在 Visual Studio 中打开项目:
cd HostedServiceSample
code .
本章的所有代码示例都可以在本书的 GitHub repo 中找到:https://github.com/PacktPublishing/Customizing-ASP.NET-Core-5.0/tree/main/Chapter05 。
I 生成 IHostedService
自 ASP.NET Core 2.0 以来,托管服务一直是一个事物,可用于在应用后台异步运行任务。它们可以用于定期获取数据、在后台进行一些计算或进行一些清理。您还可以使用它们发送预配置的电子邮件,或者您需要在后台执行的任何操作。
托管服务基本上是实现IHostedService接口的简单类。您可以使用以下代码调用它们:
public class SampleHostedService : IHostedService
{
public Task StartAsync(CancellationToken
cancellationToken)
{
}
public Task StopAsync(CancellationToken
cancellationToken)
{
}
}
IHostedService需要实现StartAsync()方法和StopAsync()方法。StartAsync()方法是实现要执行的逻辑的地方。此方法在应用启动后立即执行一次。另一方面,StopAsync()方法在应用停止之前执行。这也意味着要启动定时服务,您需要自己在上实现它。您需要实现一个定期执行代码的循环。
要执行IHostedService,您需要在 ASP.NET Core dependency injection 容器中将其注册为 singleton 实例:
services.AddSingleton<IHostedService,
SampleHostedService>();
下一个示例将向您展示托管服务的工作方式。它会在启动、停止和每 2 秒向控制台写入一条日志消息:
-
首先,编写通过
DependencyInjectionpublic class SampleHostedService : IHostedService { private readonly ILogger<SampleHostedService> logger; // inject a logger public SampleHostedService(ILogger<SampleHostedService> logger) { this.logger = logger; } public Task StartAsync(CancellationToken cancellationToken) { } public Task StopAsync(CancellationToken cancellationToken) { } }检索
ILogger的类骨架 -
下一步是实现
StopAsync方法。此方法用于在需要关闭连接、流等时进行清理:public Task StopAsync(CancellationToken cancellationToken) { logger.LogInformation("Hosted service stopping"); return Task.CompletedTask; } -
实际工作将在
StartAsyncpublic Task StartAsync(CancellationToken cancellationToken) { logger.LogInformation("Hosted service starting"); return Task.Factory.StartNew(async () => { // loop until a cancelation is requested while (!cancellationToken.IsCancellationRequested) { logger.LogInformation($"Hosted service executing - {DateTime.Now}"); try { // wait for 2 seconds await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken); } catch (OperationCanceledException) { } } }, cancellationToken); }中完成
-
To test this, start the application by calling the next command in the console:
dotnet run或在 Visual Studio 或 VS 代码中按F5。此导致以下控制台输出:

图 5.2–dotnet 运行输出的屏幕截图
如您所见,日志输出每隔 2 秒写入控制台。
在下一节中,我们将了解BackgroundService。
介绍后台服务
BackgroundService类是 ASP.NET Core 3.0 中的新类,基本上是一个已经实现了IHostedService接口的抽象类。它还提供了一个抽象的方法ExecuteAsync(),返回一个Task类型。
如果要重用上一节中的托管服务,则需要重写代码。按照以下步骤学习如何:
-
First, write the class skeleton that retrieves
ILoggerviaDependencyInjection:public class SampleBackgroundService : BackgroundService { private readonly ILogger<SampleHostedService> logger; // inject a logger public SampleBackgroundService( ILogger<SampleHostedService> logger) { this.logger = logger; } }您可能希望在文件开头添加以下
using语句:using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System.Threading; -
下一步是覆盖
StopAsync方法:public override async Task StopAsync(CancellationToken cancellationToken) { logger.LogInformation("Background service stopping"); await Task.CompletedTask; } -
In the last step, we will override the
ExecuteAsyncmethod that does all the work:protected override async Task ExecuteAsync(CancellationToken cancellationToken) { logger.LogInformation("Background service starting"); await Task.Factory.StartNew(async () => { while (!cancellationToken.IsCancellationRequested) { logger.LogInformation($"Background service executing - {DateTime.Now}"); try { await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken); } catch (OperationCanceledException) {} } }, cancellationToken); }甚至注册也是新的。
此外,在 ASP.NET Core 3.0 中,ServiceCollection有一个新的扩展方法来注册托管服务或后台工作程序:
services.AddHostedService<SampleBackgroundService>();
接下来,让我们看一看 WorkService Pro PosialT0.
实施新员工服务项目
新的工作者服务和 ASP.NET Core 3.0 及更高版本中的通用宿主使得创建简单的类似服务的应用变得非常容易,这些应用可以在没有完整的 ASP.NET 堆栈和 web 服务器的情况下完成一些工作。
可以使用以下命令创建此项目:
dotnet new worker -n BackgroundServiceSample -o BackgroundServiceSample
基本上,这将创建一个控制台应用,其中包含Program.cs和Worker.cs。Worker.cs文件包含BackgroundService类。Program.cs文件看起来很熟悉,但没有WebHostBuilder:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[]
args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHostedService<Worker>();
});
}
这将创建一个启用依赖项注入的IHostBuilder类型。这意味着我们能够在任何类型的应用中使用依赖项注入,而不仅仅是在 ASP.NET Core 应用中。
然后工人被添加到服务集合中。
这在哪里有用?您可以将此应用作为 Windows 服务或 Docker 容器中的后台应用运行,而不需要 HTTP endpoint。
总结
您现在可以开始使用IHostedService和BackgroundService来做一些更复杂的事情。小心后台服务,因为它们都在同一个应用中运行;如果您使用太多的 CPU 或内存,这可能会降低应用的速度。
对于更大的应用,我建议在专门用于执行后台任务的单独应用中运行此类任务:一个单独的 Docker 容器、Azure 上的BackgroundWorker类型、Azure 函数或类似的东西。但是,在这种情况下,它应该与主应用分开。
在下一章中,我们将学习中间件,以及如何使用中间件在请求管道上实现特殊逻辑,或在不同路径上提供特定逻辑。
六、编写自定义中间件
哇,我们已经到了这本书的第六章了!在本章中,我们将进一步了解中间件以及如何使用它来定制应用。我们将快速浏览中间件的基础知识,然后我们将探索使用中间件可以做的一些特殊事情。
我们将介绍以下主题:
- 引入中间件
- 编写自定义中间件
- 探索中间件的潜力
- 在 ASP.NET Core 3.0 及更高版本上使用中间件
本章的主题涉及 ASP.NET Core 架构的中间件层:

图 6.1–ASP.NET Core 体系结构
技术要求
要遵循本章中的描述,您需要创建一个 ASP.NET Core MVC 应用。打开控制台、shell 或 bash 终端,并切换到工作目录。使用以下命令创建新的 MVC 应用:
dotnet new web -n MiddlewaresSample -o MiddlewaresSample
现在,通过双击项目文件在 Visual Studio 中打开项目,或者通过在已打开的控制台中键入以下命令在 VS 代码中打开项目:
cd MiddlewaresSample
code .
本章的所有代码示例都可以在本书的 GitHub repo 中找到:https://github.com/PacktPublishing/Customizing-ASP.NET-Core-5.0/tree/main/Chapter06 。
I 生成中间件
大多数读者可能已经知道什么是中间件,但你们中的一些人可能不知道。即使您已经使用 ASP.NET Core 一段时间了,您也不需要了解中间件的详细信息,因为它们大多隐藏在命名良好的扩展方法后面,例如UseMvc()、UseAuthentication()、UseDeveloperExceptionPage()等等。每次在Startup.cs文件中调用Use方法时,在Configure方法中,您都会隐式使用至少一个,甚至更多的中间件。
中间件是一段处理请求管道的代码。将请求管道想象成一个巨大的管道,你可以调用一些东西,然后返回一个回音。中间件负责创建该回声、操纵声音以丰富信息、处理源声音或处理回声。
中间件按照其配置的顺序执行。配置的第一个中间件是第一个执行的中间件。
在 ASP.NET Core web 中,如果客户端请求图像或任何其他静态文件,StaticFileMiddleware类型将搜索该资源,如果找到,则返回该资源。如果没有,这个中间件除了调用下一个之外什么也不做。如果没有处理请求管道的最终中间件,则请求不返回任何内容。MvcMiddleware类型还检查请求的资源,尝试将其映射到配置的路由,执行控制器,创建视图,并返回 HTML 或 Web API 结果。如果MvcMiddleware类型找不到匹配的控制器,则返回结果;在这种情况下,它是一个404 状态结果。它在任何情况下都会返回回声。这就是为什么类型是最后配置的中间件:

图 6.2——中间件工作流程图
异常处理中间件通常是最先配置的中间件之一,不是因为它是第一个执行的,而是因为它是最后一个。配置的第一个中间件也是最后一个要执行的中间件,如果回声从管道返回。异常处理中间件验证结果,并以友好的方式在浏览器和客户端中显示可能的异常。这是运行时错误获取500 状态的地方:
-
如果按照技术要求一节中的描述创建一个空的 ASP.NET Core 应用,您可以看到管道是如何执行的。
-
Open
Startup.cswith your favorite editor. It should be pretty empty compared to a regular ASP.NET Core application. Rewrite theConfigure()method like this:public class Startup { public void ConfigureServices(IServiceCollection services) { } public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.Run(async context => { await context.Response.WriteAsync("Hello World!"); }); } }这里使用的是
DeveloperExceptionPageMiddleware类型,带有一个特殊的 lambda 中间件,它只向响应流写入"Hello World!"。响应流是我们之前了解到的回声。这个特殊的中间件停止管道并返回类似于回声的内容。因此,它是最后一个运行的中间件。 -
Leave this middleware and add the following lines right before the
app.Run()function:app.Use(async (context, next) =>{ await context.Response.WriteAsync("==="); await next(); await context.Response.WriteAsync("==="); }); app.Use(async (context, next) => { await context.Response.WriteAsync(">>>>>> "); await next(); await context.Response.WriteAsync(" <<<<<<"); });app.Use()的这两个调用也创建了两个 lambda 中间件,但这一次,除了处理特定的请求外,中间件还调用了它们的继承者:每个中间件都知道应该遵循哪个中间件,并调用它。在调用下一个中间件之前和之后,这两个中间件都会写入响应流。在调用下一个中间件之前,处理实际请求,在调用下一个中间件之后,处理响应(echo)。这应该说明管道是如何工作的。 -
如果您现在运行应用(使用
dotnet run并在浏览器中打开显示的 URL,您应该会看到如下明文结果:===>>>>>> Hello World! <<<<<<===
这对你有意义吗?如果是,那么让我们继续,看看如何使用这个概念向请求管道 ne 添加一些额外的功能。
编写自定义中间件
ASP.NET Core 是基于中间件的。请求期间执行的所有逻辑都基于中间件。因此,我们可以使用它向 web 添加自定义功能。我们想知道通过请求管道的每个请求的执行时间:
-
We can do this by creating and starting a stopwatch before the next middleware is called, and by stopping measuring the execution time after the next middleware is called, like so:
app.Use(async (context, next) => { var s = new Stopwatch(); s.Start(); // execute the rest of the pipeline await next(); s.Stop(); //stop measuring var result = s.ElapsedMilliseconds; // write out the milliseconds needed await context.Response.WriteAsync($"Time needed: {result}"); });之后,我们将经过的毫秒数返回到响应流。
-
If you write some more middleware, the
Configuremethod inStartup.csgets pretty messy. This is why most middleware is written as separate classes. This could look like this:public class StopwatchMiddleWare { private readonly RequestDelegate _next; public StopwatchMiddleWare(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { var s = new Stopwatch(); s.Start(); // execute the rest of the pipeline await _next(context); s.Stop(); //stop measuring var result = s.ElapsedMilliseconds; // write out the milliseconds needed await context.Response.WriteAsync($"Time needed: {result}"); } }这样,我们通过构造函数和
Invoke()方法中的当前上下文获得下一个中间件。笔记
中间件在应用启动时初始化,构造函数在应用生命周期内只运行一次。另一方面,每个请求调用一次
Invoke()方法。 -
要使用这个中间件,您可以在
configure方法中使用一个通用的UseMiddleware()方法:app.UseMiddleware<StopwatchMiddleware>(); -
更优雅的方法是创建一个封装此调用的
extension方法:public static class StopwatchMiddlewareExtension { public static IApplicationBuilder UseStopwatch(this IApplicationBuilder app) { app.UseMiddleware<StopwatchMiddleware>(); return app; } } -
现在你可以简单地这样称呼它:
app.UseStopwatch();
这是通过请求管道向 ASP.NET Core 应用提供附加功能的方法。您的中间件中提供了整个HttpContext。有了它,您可以使用中间件操纵请求甚至响应。
例如,AuthenticationMiddleware类型试图从请求中收集用户信息。如果找不到,它将通过向客户端发送特定的响应来请求信息。如果它找到一些信息,它会将其添加到请求上下文中,并使整个应用可以使用它的方式。
挖掘中间件的潜力
你可以用中间件做很多其他的事情。例如,您知道可以将请求管道拆分为两个或多个管道吗?在本节中,我们将介绍如何做到这一点以及其他一些事情。
使用/map 对管道进行分支
下一个片段展示了如何基于特定路径创建分支:
app.Map("/map1", app1 =>
{
// some more Middleware
app1.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1");
});
});
app.Map("/map2", app2 =>
{
// some more Middleware
app2.Run(async context =>
{
await context.Response.WriteAsync("Map Test 2");
});
});
// some more Middleware
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
路径"/map1"是一个特定的分支,它继续内部的请求管道–与"/map2"相同。这两个映射内部都有自己的中间件配置。所有其他未指定的路径将跟随主分支。
使用 MapWhen()对管道进行分支
还有一种MapWhen()方法可以基于条件而不是基于路径的分支来分支管道:
public void Configure(IApplicationBuilder app)
{
app.MapWhen(
context =>
context.Request.Query.ContainsKey("branch"),
app1 =>
{
// some more Middleware
app1.Run(async context =>
{
await context.Response.WriteAsync(
"MapBranch Test");
});
});
// some more Middleware
app.Run(async context =>
{
await context.Response.WriteAsync(
"Hello from non-Map delegate.");
});
}
接下来,我们将看看如何使用中间件来创造条件。
用中间件创造条件
您可以根据配置值创建条件,或者如图所示,根据请求上下文的属性创建条件。在上一个示例中,使用了查询字符串属性。您可以使用 HTTP 头、表单属性或请求上下文的任何其他属性。
如果需要,还可以嵌套贴图以创建子分支和孙分支。
Map()或MapWhen()可用于根据特定路径或特定条件提供特定 API 或资源。ASP.NET CoreHealthCheckAPI 是这样做的:首先使用MapWhen()指定要使用的端口,然后使用Map()设置HealthCheckAPI 的路径(如果没有指定端口,则使用Map()。最后使用了HealthCheckMiddleware型。下面的代码只是一个示例,展示了它的外观:
private static void UseHealthChecksCore(IApplicationBuilder
app, PathString path, int? port, object[] args)
{
if (port == null)
{
app.Map(path,
b =>
b.UseMiddleware<HealthCheckMiddleware>(args));
}
else
{
app.MapWhen(
c => c.Connection.LocalPort == port,
b0 => b0.Map(path,
b1 =>
b1.UseMiddleware<HealthCheckMiddleware> (args)
)
);
}
}
接下来,让我们看看您应该如何在更新版本的 ASP.NET Core 中使用终止中间件。
在 ASP.NET Core 3.0 及更高版本上使用中间件
在 ASP.NETCore 3.0 及更高版本中,Configure()方法看起来有所不同。有两种新的中间件元素,称为UseRouting和UseEndpoints:
public void Configure(IApplicationBuilder app,
IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hell World!");
});
});
}
第一个是使用路由的中间件,另一个使用端点。我们到底在看什么?
这是新的端点路由。以前,路由是 MVC 的一部分,只适用于 MVC、Web API 和基于 MVC 框架的框架。然而,在 ASP.NET Core 3.0 及更高版本中,路由不再在 MVC 框架中。现在,MVC 和其他框架被映射到特定的路由或端点。有不同类型的端点定义可用。
在前面的代码片段中,GET请求被映射到页面根 URL。在下一个片段中,MVC 被映射到一个路由模式,剃须刀页面被映射到剃须刀页面特定的基于文件结构的路由:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
不再有UseMvc(),即使它仍然存在并且仍然在IApplicationBiulder级别工作,以防止现有代码被破坏。现在,有一些新方法可以更精确地激活 ASP.NET Core 功能。
以下是 ASP.NET Core 5.0 最常用的新Map方法:
- MVC 和 WebAPI 区域:
endpoints.MapAreaControllerRoute(...); - MVC 和 WebAPI:
endpoints.MapControllerRoute(...); - Blazor 服务器端:
endpoints.MapBlazorHub(...); - 信号机:
endpoints.MapHub(...); - 剃须刀页面:
endpoints.MapRazorPages(...); - 健康检查:
endpoints.MapHealthChecks(...);
有很多方法可以定义回退端点,并将路由和 HTTP 方法映射到委托和中间件。
如果您想要创建在所有请求上都能工作的中间件,比如StopWatchMiddleware类型,那么这将像以前在IApplicationBuilder类型上一样工作。如果您想编写一个中间件来处理特定的路径或路由,则需要创建一个Map方法,使其映射到该路由。
重要提示
不再建议在中间件内部处理路由。通过这种方法,中间件更加通用,可以使用单个配置在多个路由上工作。
我最近编写了一个中间件,在 ASP.NET Core 应用中提供 GraphQL 端点;我不得不重写它以遵循新的 ASP.NET Core 路由。旧方法仍然可以工作,但是可以将路径和路由与新的 ASP.NET Core 路由分开处理。让我们看看如何处理这些情况。
将终止中间件重写为当前标准
如果您有提供不同端点的现有中间件,则应将其更改为使用新端点路由:
-
As an example, let's create a small dummy middleware that writes an application status to a specific route. In this demo, there is no custom route handling:
public class AppStatusMiddleware { private readonly RequestDelegate _next; private readonly string _status; public AppStatusMiddleware( RequestDelegate next, string status) { _next = next; _status = status; } public async Task Invoke(HttpContext context) { await context.Response.WriteAsync( $"Hello {_status}!"); } }我们需要做的第一件事是在
IEndpointRouteBuilder类型上编写一个extension方法。此方法有一个路由模式作为可选参数,并返回一个IEndpointConventionBuilder类型以启用路由的 CORS、身份验证或其他条件。 -
现在我们应该添加一个扩展方法,以便更容易地使用中间件:
public static class MapAppStatusMiddlewareExtension { public static IEndpointConventionBuilder MapAppStatus( this IEndpointRouteBuilder routes, string pattern = "/", string name = "World") { var pipeline = routes .CreateApplicationBuilder() .UseMiddleware<AppStatusMiddleware>(name) .Build(); return routes.Map(pattern, pipeline) .WithDisplayName("AppStatusMiddleware"); } } -
一旦完成,我们就可以使用
MapAppStatus方法将其映射到特定的路径:app.UseEndpoints(endpoints => { endpoints.MapGet("/", async context => { await context.Response.WriteAsync( "Hello World!"); }); endpoints.MapAppStatus("/status", "Status"); }); -
我们现在可以在浏览器中调用路由:
http://localhost:5000/status。
我们将在第 13 章使用端点路由中学习关于端点路由的更多信息以及如何对其进行定制。现在,让我们回顾一下我们对中间件的了解。
总结
大多数 ASP.NET Core 功能都基于中间件,我们可以通过创建自己的中间件来扩展 ASP.NET Core。
在接下来的两章中,我们将研究不同的数据类型,以及如何使用内容协商来处理它们。我们将学习如何创建任何格式和数据类型的 API 输出。
七、将自定义OutputFormatter用于内容协商
在第七章中,我们将学习如何以不同的格式和类型向客户机发送数据。默认情况下,ASP.NET Core Web API 以 JSON 的形式发送数据,但还有其他一些方法来分发数据。
我们将介绍以下部分:
- 介绍
OutputFomatters - 创建自定义
OutputFormatters
本章的主题涉及 ASP.NET Core 架构的WebAPI层:

图 7.1–ASP.NET Core 体系结构
技术要求
要遵循本章中的描述,您需要创建一个 ASP.NET Core MVC 应用。打开控制台、shell 或 bash 终端,并切换到工作目录。使用以下命令创建新的 MVC 应用:
dotnet new webapi -n OutputFormatterSample -o OutputFormatterSample
现在,通过双击项目文件在 Visual Studio 中打开项目,或者通过在已打开的控制台中键入以下命令在 VS 代码中打开项目:
cd OutputFormatterSample
code .
本章的所有代码示例都可以在本书的 GitHub repo 中找到:https://github.com/PacktPublishing/Customizing-ASP.NET-Core-5.0/tree/main/Chapter07 。
I 生成输出格式化程序
OutputFormatters是类,将现有数据转换为不同的格式,通过 HTTP 发送给客户端。Web API 使用默认的OutputFormatters将对象转换为 JSON,这是发送结构化数据的默认格式。其他内置格式化程序包括 XML 格式化程序和纯文本格式化程序。
通过所谓的内容协商,客户能够决定他们想要检索的格式。客户端需要在Accept头中指定格式的内容类型。内容协商在ObjectResult中实施。
默认情况下,Web API 始终返回 JSON,即使您在标题中接受 text/XML。这就是默认情况下不注册内置 XML 格式化程序的原因。
有两种方法可以将XmlSerializerOutputFormatter添加到 ASP.NET Core。第一个显示在以下代码段中:
services.AddControllers()
.AddXmlSerializerFormatters();
或者,您也可以使用以下选项:
services.AddControllers()
.AddMvcOptions(options =>
{
options.OutputFormatters.Add(
new XmlSerializerOutputFormatter());
});
您可能需要将Microsoft.AspNetCore.Mvc.Formatters名称空间添加到using语句中。
还有XmlDataContractSerializerOutputFormatter可以在内部使用DataContractSerializer,配置更灵活。
默认情况下,任何Accept头将自动转换为application/json,即使您使用这些方法之一。但是,我们可以解决这个问题。
如果要允许客户端接受不同的头,需要关闭该翻译:
services.AddControllers()
.AddMvcOptions(options =>
{
options.RespectBrowserAcceptHeader = true;
// false by default
});
一些不完全支持 ASP.NET Core 5.0 的第三方组件不会异步写入响应流,而是默认配置,因为 ASP.NET Core 3.0仅允许异步写入。
要启用同步写入访问,您需要将以下行添加到ConfigureServices方法中:
services.Configure<KestrelServerOptions>(options =>
{
options.AllowSynchronousIO = true;
});
将Microsoft.AspNetCore.Server.Kestrel.Core名称空间添加到using语句中以访问选项。
为了尝试格式化程序,让我们建立一个小测试项目。
准备测试项目
使用控制台,我们将创建一个小型 ASP.NET Core Web API 项目:
-
Execute the following commands to add the necessary NuGet packages:
dotnet add package GenFu dotnet add package CsvHelper这将创建一个新的 webapi 项目,并向其中添加两个 NuGet 包。
GenFu是一个非常棒的库,可以轻松创建测试数据。第二个包CsvHelper帮助我们轻松编写 CSV 数据。现在,在 Visual Studio 或 VS 代码中打开该项目,并在
controller文件夹中创建一个名为PersonsController的新控制器:[Route("api/[controller]")] [ApiController] public class PersonsController : ControllerBase { } -
Open
PersonsController.csand add aGet()method like this:[HttpGet] public ActionResult<IEnumerable<Person>> Get() { var persons = A.ListOf<Person>(25); return persons; }您可能需要在文件开头添加以下
using语句:using GenFu; using OutputFormatterSample.models;这将使用
GenFu创建一个 25 人的列表。属性将自动填充真实数据。GenFu是一款开源、快速、轻量级、可扩展的测试数据生成器。它包含名称、城市、国家/地区、电话号码等的内置列表,并根据属性名称自动将数据填充到类的正确属性中。一个名为City的属性将填写一个城市的名称,而一个名为Phone、Telephone或Phonenumber的属性将填写一个格式正确的假电话号码。稍后,您将看到 GenFu 的魔力和结果。 -
创建一个
models文件夹并创建一个新文件Person.cs,其中包含Person类:public class Person { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } public string EmailAddress { get; set; } public string Address { get; set; } public string City { get; set; } public string Phone { get; set; } } -
Open
Startup.csas well, add the XML formatters, and allow other Accept headers, as described earlier:services.AddControllers() .AddMvcOptions(options => { options.RespectBrowserAcceptHeader = true; // false by default options.OutputFormatters.Add( new XmlSerializerOutputFormatter()); });现在就到这里。现在您可以从 Web API 检索数据了。
-
Start the project by using the
dotnet runcommand.接下来,我们将测试 API。
测试 Web API
测试 Web API 的最佳工具是小提琴手(https://www.telerik.com/fiddler 或邮递员(https://www.postman.com/ 。我更喜欢邮递员,因为我觉得它更容易使用。最后,你想用哪种工具并不重要;在这些演示中,我们将使用邮递员:
-
In Postman, create a new request. Write the API URL, which is
https://localhost:5001/api/persons, into theaddressfield, and add a header with the key asAcceptand the value asapplication/json.点击发送后,会在响应体中看到 JSON 结果,如下图所示:
![Figure 7.2 – A screenshot of JSON output in Postman]()
图 7.2–Postman 中 JSON 输出的屏幕截图
在这里,您可以看到自动生成的值。
GenFu根据财产类型和财产名称,将数据放入此人的财产中:真实的名字和真实的姓氏,以及真实的城市和正确格式的电话号码。 -
接下来,让我们测试 XML 输出格式化程序。在邮递员界面,将
Accept头从application/json改为text/xml,点击发送:

图 7.3–Postman 中 XML 输出的屏幕截图
我们现在有一个 XML 格式的输出。
现在,让我们更进一步,创建一些定制OutputFormatters。
创建自定义输出格式化程序
计划是创建一个VCard输出,以便能够将此人的联系人详细信息直接导入 Outlook 或支持 VCard 的任何其他联系人数据库。在本节后面,我们还想创建一个 CSV 输出格式化程序。
两者都是基于文本的输出格式化程序,将从TextOutputFormatter中派生其值:
-
在名为
VcardOutputFormatter.cs的新文件中创建一个新类。 -
Now, insert the following class skeleton in the new file. You will find the implementations of the empty methods in the following snippets. The constructor contains the supported media types and content encodings:
public class VcardOutputFormatter : TextOutputFormatter { public string ContentType { get; } public VcardOutputFormatter() { SupportedMediaTypes.Add( MediaTypeHeaderValue.Parse("text/vcard")); SupportedEncodings.Add(Encoding.UTF8); SupportedEncodings.Add(Encoding.Unicode); } protected override bool CanWriteType(Type type) { } public override Task WriteResponseBodyAsync( OutputFormatterWriteContext context, Encoding selectedEncoding) { } private static void FormatVcard( StringBuilder buffer, Person person, ILogger logger) { } }您可能需要添加以下
using语句:using Microsoft.AspNetCore.Mvc.Formatters; using System.Text; using Microsoft.Extensions.Logging; using Microsoft.Net.Http.Headers; -
下一个代码片段显示了
CanWriteType方法的实现。重写此方法是可选的,但将其限制在特定条件下是有意义的。在这种情况下,OutputFormatter只能格式化Person:protected override bool CanWriteType(Type type) { if (typeof(Person).IsAssignableFrom(type) || typeof(IEnumerable<Person>) .IsAssignableFrom(type)) { return base.CanWriteType(type); } return false; }类型的对象
-
You need to override
WriteResponseBodyAsyncto convert the actualPersonobjects into the output you want to have. To get the objects to convert, you need to extract them fromOutputFormatterWriteContextthat gets passed into the method. You also get the HTTP response from this context. This is needed to write the results and send them to the client.在方法内部,我们检查是否得到一个人或一组人,并调用尚未实现的
FormatVcard方法:public override Task WriteResponseBodyAsync( OutputFormatterWriteContext context, Encoding selectedEncoding) { var serviceProvider = context.HttpContext.RequestServices; var logger = serviceProvider.GetService( typeof(ILogger<VcardOutputFormatter>)) as ILogger; var response = context.HttpContext.Response; var buffer = new StringBuilder(); if (context.Object is IEnumerable<Person>) { foreach (var person in context.Object as IEnumerable<Person>) { FormatVcard(buffer, person, logger); } } else { var person = context.Object as Person; FormatVcard(buffer, person, logger); } return response.WriteAsync(buffer.ToString()); } -
要格式化输出以支持标准
VCard,您需要做一些手工工作:private static void FormatVcard( StringBuilder buffer, Person person, ILogger logger) { buffer.AppendLine("BEGIN:VCARD"); buffer.AppendLine("VERSION:2.1"); buffer.AppendLine( $"FN:{person.FirstName} {person.LastName}"); buffer.AppendLine( $"N:{person.LastName};{person.FirstName}"); buffer.AppendLine( $"EMAIL:{person.EmailAddress}"); buffer.AppendLine( $"TEL;TYPE=VOICE,HOME:{person.Phone}"); buffer.AppendLine( $"ADR;TYPE=home:;;{person.Address};{person. City}"); buffer.AppendLine($"UID:{person.Id}"); buffer.AppendLine("END:VCARD"); logger.LogInformation( $"Writing {person.FirstName} {person.LastName}"); } -
然后我们需要在
Startup.csservices.AddControllers() .AddMvcOptions(options => { options.RespectBrowserAcceptHeader = true; // false by default options.OutputFormatters.Add( new XmlSerializerOutputFormatter()); // register the VcardOutputFormatter options.OutputFormatters.Add( new VcardOutputFormatter()); });中注册新的
VcardOutputFormatter类型 -
使用
dotnet run再次启动应用。 -
Now, change the
Acceptheader totext/vcardand let's see what happens:![Figure 7.4 – A screenshot of VCard output in Postman]()
图 7.4–Postman 中 VCard 输出的屏幕截图
我们现在应该以
VCard格式查看所有数据。 -
让我们对 CSV 输出执行同样的操作。我们已经在项目中添加了
CsvHelper库。现在转到以下 URL 并下载CsvOutputFormatter将其放入您的项目中:https://github.com/PacktPublishing/Customizing-ASP.NET-Core-5.0/blob/main/Chapter07/OutputFormatterSample5.0/CsvOutputFormatter.cs 。 -
让我们快速来看看
WriteResponseBodyAsync方法:
```cs
public override async Task WriteResponseBodyAsync(
OutputFormatterWriteContext context,
Encoding selectedEncoding)
{
var response = context.HttpContext.Response;
var csv = new CsvWriter(
new StreamWriter(response.Body),
CultureInfo.InvariantCulture);
IEnumerable<Person> persons;
if (context.Object is IEnumerable<Person>)
{
persons = context.Object as
IEnumerable<Person>;
}
else
{
var person = context.Object as Person;
persons = new List<Person> { person };
}
await csv.WriteRecordsAsync(persons);
}
```
- 这几乎与
VcardOutputFormatter的工作方式相同。我们可以通过StreamWriter将响应流直接传递到CsvWriter。在那之后,我们可以把这些人或名单提供给作者。就这样。 - 我们还需要注册
CsvOutputFormatter才能测试:
```cs
services.AddControllers()
.AddMvcOptions(options =>
{
options.RespectBrowserAcceptHeader = true;
// false by default
options.OutputFormatters.Add(
new XmlSerializerOutputFormatter());
// register the VcardOutputFormatter
options.OutputFormatters.Add(
new VcardOutputFormatter());
// register the CsvOutputFormatter
options.OutputFormatters.Add(
new CsvOutputFormatter());
});
```
- 在邮递员界面,将
Accept标题改为text/csv,再次点击发送:

图 7.5–Postman 中文本/CSV 输出的屏幕截图
好了:邮递员能够打开我们测试的所有格式。
总结
那不是很酷吗?基于Accept头更改格式的功能非常方便。通过这种方式,您可以为许多不同的客户机创建一个 Web API:一个根据客户机的首选项接受多种不同格式的 Web API。仍然有很多潜在客户机不使用 JSON,而更喜欢 XML 或 CSV。
另一种方法是选择使用 CSV 数据或 Web API 中的任何其他格式。假设您的客户向您发送 CSV 格式的人员列表。你将如何解决这个问题?在action方法中手动解析字符串是可行的,但这不是一个容易的选择。
这就是ModelBinders能为我们做的。让我们在下一章中看看它是如何工作的。
八、使用自定义ModelBinder管理输入
在关于OutputFormatters的最后一章中,我们学习了如何以不同的格式向客户发送数据。在本章中,我们将以另一种方式进行。本章是关于您从外部在 Web API 中获取的数据;例如,如果您以特殊格式获取数据,或者如果您以特殊方式获取需要验证的数据,该怎么办。模型绑定器将帮助您处理此问题。
在本章中,我们将介绍以下主题:
- 介绍
ModelBinders - 创建自定义
ModelBinder类型
本章中的主题涉及 ASP.NET Core 架构的WebAPI层:

图 8.1–ASP.NET Core 体系结构
技术要求
要遵循本章中的描述,您需要创建一个 ASP.NET Core MVC 应用。打开控制台、shell 或 bash 终端,并切换到工作目录。使用以下命令创建新的 MVC 应用:
dotnet new webapi -n ModelBinderSample -o ModelBinderSample
现在,在 Visual Studio 中双击项目文件或在 VS 代码中,在已打开的控制台中键入以下命令来打开项目:
cd ModelBinderSample
code .
本章中的所有代码示例都可以在本书的 GitHub 存储库中的中找到 https://github.com/PacktPublishing/Customizing-ASP.NET-Core-5.0/tree/main/Chapter08 。
I 生成模型绑定器
ModelBinders负责将传入数据绑定到具体的动作方法参数。它们将随请求发送的数据绑定到参数。默认绑定器能够绑定通过QueryString发送的数据,或在请求正文中发送的数据。在主体内,数据可以 URL 格式或 JSON 格式发送。
模型绑定尝试通过参数名查找请求中的值。表单值、路由数据和查询字符串值存储为键值对集合,绑定尝试在集合的键中查找参数名称。
让我们用一个测试项目来演示它是如何工作的。
准备测试数据
在部分中,我们将了解如何将 CSV 数据发送到 Web API 方法。我们将使用自定义 OutputFormatter 重用我们在第 7 章内容协商中创建的 CSV 数据:
这是我们要使用的测试数据片段:
Id,FirstName,LastName,Age,EmailAddress,Address,City,Phone
48,Austin,Ward,49,Jake.Timms@live.com,"8814 Gravesend Neck Road ",Daly City,(620) 260-4410
2,Sierra,Smith,15,Elizabeth.Wright@hotmail.com,"1199 Marshall Street ",Whittier,(655) 379-4362
27,Victorina,Radcliff,40,Bryce.Sanders@rogers.ca,"2663 Sutton Street ",Bloomington,(255) 365-0521
78,Melissa,Brandzin,39,Devin.Wright@telus.net,"7439 Knight Court ",Tool,(645) 343-2144
89,Kathryn,Perry,87,Hailey.Jenkins@hotmail.com,"5283 Vanderbilt Street ",Carlsbad,(747) 369-4849
您可以在 GitHub 的上找到完整的 CSV 测试数据 https://github.com/PacktPublishing/Customizing-ASP.NET-Core-5.0/blob/main/Chapter08/testdata.csv 。
准备测试项目
让我们按照以下步骤准备项目:
-
In the already created project (refer to the Technical requirements section), we will now create a new empty API controller with a small action inside:
namespace ModelBinderSample.Controllers { [Route("api/[controller]")] [ApiController] public class PersonsController : ControllerBase { public ActionResult<object> Post( IEnumerable<Person> persons) { return new { ItemsRead = persons.Count(), Persons = persons }; } } }这看起来基本上和其他任何动作一样。它接受一个人员列表并返回一个匿名对象,该对象包含人员数量和人员列表。此操作非常无用,但有助于我们使用 Postman 调试
ModelBinders。 -
We also need the
Personclass:[ModelBinder(BinderType = typeof(PersonsCsvBinder))] public class Person { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } public string EmailAddress { get; set; } public string Address { get; set; } public string City { get; set; } public string Phone { get; set; } }如果我们希望将基于 JSON 的数据发送到该操作,那么这实际上可以很好地工作。
-
As a last preparation step, we need to add the
CsvHelperNuGet package to parse the CSV data more easily. The .NET CLI is also useful here:dotnet add package CsvHelper dotnet add package System.Linq.Async笔记
需要
System.Linq.Async包来处理GetRecordsAsync()方法返回的IAsyncEnumerable。
现在都设置好了,我们可以在下一个门派中尝试并创建PersonsCsvBinder。
创建人员 CSVBinder
让我们做一个活页夹。
要创建ModelBinder,请添加一个名为PersonsCsvBinder的新类,该类实现了IModelBinder。在BindModelAsync方法中,我们得到ModelBindingContext,其中包含获取数据和反序列化所需的所有信息。下面的代码片段显示了一个通用绑定器,它可以与任何模型列表一起使用。我们将其分为几个部分,以便您可以清楚地看到活页夹的每个部分是如何工作的:
public class PersonsCsvBinder : IModelBinder
{
public async Task BindModelAsync(
ModelBindingContext bindingContext)
{
if (bindingContext == null)
{
return;
}
var modelName = bindingContext.ModelName;
if (String.IsNullOrEmpty(modelName))
{
modelName = bindingContext.OriginalModelName;
}
if (String.IsNullOrEmpty(modelName))
{
return;
}
正如您从前面的代码块中所看到的,首先,上下文被检查为 null。之后,如果没有指定参数名称,我们将为模型设置一个默认参数名称。如果完成此操作,我们可以按照前面设置的名称获取值:
var valueProviderResult =
bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult == ValueProviderResult.None)
{
return;
}
在下一部分中,如果没有值,我们不应该在这种情况下抛出异常。原因是下一个配置的ModelBinder可能负责。如果抛出异常,当前请求的执行被取消,下一个配置的ModelBinder没有执行的机会:
bindingContext.ModelState.SetModelValue(
modelName, valueProviderResult);
var value = valueProviderResult.FirstValue;
// Check if the argument value is null or empty
if (String.IsNullOrEmpty(value))
{
return;
}
如果我们有值,我们可以实例化一个需要传递给CsvReader的新StringReader类型:
var stringReader = new StringReader(value);
var reader = new CsvReader(
stringReader, CultureInfo.InvariantCulture);
使用CsvReader,我们可以将 CSV 字符串值反序列化为Persons列表。如果我们有该列表,我们需要创建一个新的成功的ModelBindingResult类型,该类型需要分配给ModelBindingContext的Result属性:
var asyncModel = reader.GetRecordsAsync<Person>();
var model = await asyncModel.ToListAsync();
bindingContext.Result =
ModelBindingResult.Success(model);
}
}
您可能需要在文件开头添加以下using语句:
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.IO;
using CsvHelper
using System.Globalization;
接下来,我们将使ModelBindert 工作。
使用 ModelBinder
绑定器没有自动使用,因为它没有在依赖项注入容器中注册,也没有配置为在 MVC 框架中使用。
使用此模型绑定器的最简单方法是在应绑定模型的操作参数上使用ModelBinderAttribute:
[HttpPost]
public ActionResult<object> Post(
[ModelBinder(binderType: typeof(PersonsCsvBinder))]
IEnumerable<Person> persons)
{
return new
{
ItemsRead = persons.Count(),
Persons = persons
};
}
在这里,PersonsCsvBinder的类型被设置为该属性的binderType。
笔记
Steve Gordon在他的博客文章中写到了第二个选项,ASP.NET MVC 核心中的自定义模型绑定。他使用ModelBinderProvider将ModelBinder添加到现有的列表中。
我个人更喜欢显式声明,因为大多数自定义ModelBinders将特定于动作或特定类型,并且它可以防止隐藏在背景中的魔法。
现在,让我们测试一下我们已经构建了什么。
测试模型粘合剂
为了测试它,我们需要在 Postman 中创建一个新请求:
-
通过在控制台中运行
dotnet run或在 Visual Studio 或 VS 代码中按F5启动应用。 -
首先,我们将请求类型设置为POST,并在地址栏中插入 URL
https://localhost:5001/api/persons。 -
接下来,我们需要将 CSV 数据添加到请求主体中。选择
form-data作为body类型,添加persons键,并在值字段中粘贴以下行:Id,FirstName,LastName,Age,EmailAddress,Address,City,Phone 48,Austin,Ward,49,Jake.Timms@live.com,"8814 Gravesend Neck Road ",Daly City,(620) 260-4410 2,Sierra,Smith,15,Elizabeth.Wright@hotmail.com,"1199 Marshall Street ",Whittier,(655) 379-4362 27,Victorina,Radcliff,40,Bryce.Sanders@rogers.ca,"2663 Sutton Street ",Bloomington,(255) 365-0521 -
按发送后,我们得到的结果如Fi 图 8.2所示:

图 8.2-Postman 中 CSV 数据的屏幕截图
现在,客户端将能够向服务器发送基于 CSV 的数据。
总结
这是以操作真正需要的方式转换输入的好方法。您还可以使用ModelBinders对数据库进行一些自定义验证,或者在模型传递到操作之前需要执行的任何操作。
在下一章中,我们将看到您可以使用ActionFilters做些什么。
进一步阅读
要了解有关ModelBinders的更多信息,您应该查看以下合理详细的文档:
- Steve Gordon,ASP.NET MVC 核心中的自定义模型绑定:https://www.stevejgordon.co.uk/html-encode-string-aspnet-core-model-binding/
- ASP.NET Core 中的模型绑定:https://docs.microsoft.com/en-us/aspnet/core/mvc/models/model-binding
- ASP.NET Core 中的自定义模型绑定:https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model-binding
九、创建自定义ActionFilter
在本章中,我们将继续在控制器级别进行定制。我们将研究动作过滤器以及如何创建自己的ActionFilters以保持动作的小型化和可读性。
在本章中,我们将介绍以下主题:
- 介绍
ActionFilters - 使用
ActionFilters
本章中的主题涉及 ASP.NET Core 架构的MVC层:

图 9.1–ASP.NET Core 体系结构
技术要求
要遵循本章中的描述,您需要创建一个 ASP.NET Core MVC 应用。打开控制台、shell 或 bash 终端,并切换到工作目录。使用以下命令创建新的 MVC 应用:
dotnet new mvc -n ActionFilterSample -o ActionFilterSample
现在,在 Visual Studio 中双击项目文件或在 VS 代码中,在已打开的控制台中键入以下命令来打开项目:
cd ActionFilterSample
code .
本章中的所有代码示例都可以在本书的 GitHub 存储库中的中找到 https://github.com/PacktPublishing/Customizing-ASP.NET-Core-5.0/tree/main/Chapter09 。
I 产生动作滤波器
ActionFilters是一个有点像中间件,因为它们可以操作输入和输出,但在 MVC 层的特定操作或特定控制器的所有操作上立即执行,而中间件直接在宿主层的请求对象上工作。创建ActionFilters是为了在执行操作之前或之后执行代码。它们用于执行不属于实际操作逻辑的方面:授权是这些方面的一个示例。AuthorizeAttribute用于允许用户或组访问特定操作或控制器。AuthorizeAttribute是一个ActionFilter。它检查登录的用户是否被授权。如果没有,它会将它们重定向到登录页面。
笔记
如果全局应用ActionFilter,则它将在应用中的所有操作上执行。
下面的代码示例显示了正常ActionFilter和异步ActionFilter的框架:
using Microsoft.AspNetCore.Mvc.Filters;
public class SampleActionFilter : IActionFilter
{
public void OnActionExecuting(
ActionExecutingContext context)
{
// do something before the action executes
}
public void OnActionExecuted(
ActionExecutedContext context)
{
// do something after the action executes
}
}
public class SampleAsyncActionFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
// do something before the action executes
var resultContext = await next();
// do something after the action executes
// resultContext.Result will be set
}
}
正如您在这里看到的,在执行目标操作之前和之后放置要执行的代码方面,总是有两种方法。这些ActionFilters不能用作属性。如果您想在控制器中使用ActionFilters作为属性,则需要从Attribute或ActionFilterAttribute派生,如下面的代码所示:
public class ValidateModelAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(
ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
context.Result = new BadRequestObjectResult(
context.ModelState);
}
}
}
前面的代码显示了一个简单的ActionFilter,如果ModelState无效,它总是返回一个BadRequestObjectResult。这在 Web API 中可能有用,作为对POST、PUT和PATCH请求的默认检查。这可以用更多的验证逻辑来扩展。稍后我们将了解如何使用它。
ActionFilter的另一个可能用例是日志记录。您不需要直接登录Actions控制器。您可以在操作筛选器中执行此操作,以使操作具有相关代码的可读性:
using Microsoft.Extensions.Logging;
public class LoggingActionFilter : IActionFilter
{
ILogger _logger;
public LoggingActionFilter(ILoggerFactory loggerFactory)
{
_logger =
loggerFactory.CreateLogger<LoggingActionFilter>();
}
public void OnActionExecuting(
ActionExecutingContext context)
{
_logger.LogInformation($"Action
'{context.ActionDescriptor.DisplayName}'
executing");
}
public void OnActionExecuted(
ActionExecutedContext context)
{
_logger.LogInformation($"Action
'{context.ActionDescriptor.DisplayName}'
executed");
}
}
这将向控制台记录一条信息性消息。您可以从ActionExecutingContext或ActionExecutedContext获取有关当前操作的更多信息,例如,参数或参数值。这使得和ActionFilters非常有用。
让我们看看ActionFilters在实践中是如何工作的。
使用 ActionFilter
ActionFilters可以将实际上是Attributes注册为Action或Controller的属性,从下面的演示代码中可以看到:
[HttpPost]
[ValidateModel] // ActionFilter as attribute
public ActionResult<Person> Post([FromBody] Person model)
{
// save the person
return model; //just to test the action
}
这里我们使用ValidateModelAttribute,检查ModelState,如果ModelState无效,返回BadRequestObjectResult;我们不需要在实际操作中检查ModelState。
要在全球注册ActionFilters,您需要在Startup.cs的ConfigureServices方法中扩展 MVC 注册:
services.AddControllersWithViews()
.AddMvcOptions(options =>
{
options.Filters.Add(new SampleActionFilter());
options.Filters.Add(new SampleAsyncActionFilter());
});
以这种方式注册的ActionFilters将在每个操作上执行。这样,您就可以使用不是从Attribute派生的ActionFilters。
我们之前创建的LoggingActionFilter类型更为特殊。它依赖于ILoggerFactory的一个实例,需要传递给构造函数。这不能作为属性很好地工作,因为Attributes不支持通过依赖项注入进行构造函数注入。ILoggerFactory在 ASP.NET Core 依赖项注入容器中注册,需要注入LoggingActionFilter。
因此,有更多的方式注册ActionFilters。在全局范围内,我们可以将它们注册为依赖项注入容器实例化的类型,并且依赖项可以通过容器解决:
services.AddControllersWithViews()
.AddMvcOptions(options =>
{
options.Filters.Add<LoggingActionFilter>();
})
这很有效。我们现在在过滤器中有了ILoggerFactory。
要在Attributes中支持自动解析,您需要在控制器或动作级别上使用ServiceFilterAttribute:
[ServiceFilter(typeof(LoggingActionFilter))]
public class HomeController : Controller
{
除了全局过滤器注册外,ActionFilter还需要在ServiceCollection中注册才能与ServiceFilterAttribute一起使用:
services.AddSingleton<LoggingActionFilter>();
完整地说,还有另一种使用ActionFilters的方法,需要将参数传递给构造函数。您可以使用TypeFilterAttribute自动实例化过滤器。但是,在使用该属性时,依赖项注入容器没有实例化过滤器;参数需要指定为TypeFilterAttribute的参数。
请参阅官方文档中的以下代码片段:
[TypeFilter(typeof(AddHeaderAttribute),
Arguments = new object[] { "Author", "Juergen Gutsch (@sharpcms)" })]
public IActionResult Hi(string name)
{
return Content($"Hi {name}");
}
过滤器类型和参数用TypeFilterAttribute指定。AddHeaderAttribute是 ASP.NET Core 框架提供的ActionFilter属性之一。
总结
ActionFilters给我们一个简单的方法来保持动作干净。如果我们在行动中发现重复的任务与行动的实际责任并不相关,我们可以将这些任务转移到ActionFilter,或者ModelBinder或者MiddleWare,这取决于它在全球范围内的工作需要。与某项行动越相关,使用ActionFilter就越合适。
还有其他类型的过滤器,它们都以类似的方式工作。要了解更多关于不同类型过滤器的信息,请阅读推荐的文档。
在下一章中,我们将继续讨论实际的视图逻辑,并使用定制TagHelpers扩展 Razor 视图。
进一步阅读
微软 ASP.NET Core 控制器:https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters
十、创建自定义TagHelpers
在第十章中,我们将讨论TagHelpers。内置的TagHelpers非常有用,使 Razor 代码更加美观,可读性更强。创建自定义TagHelpers将使您的生活更加轻松。
在本章中,我们将介绍以下主题:
- 介绍
TagHelpers - 创建自定义
TagHelpers
本章中的主题涉及 ASP.NET Core 架构的MVC层:

图 10.1–ASP.NET Core 体系结构
技术要求
要遵循本章中的描述,您需要创建一个 ASP.NET Core MVC 应用。打开控制台、shell 或 bash 终端,并切换到工作目录。使用以下命令创建新的 MVC 应用:
dotnet new mvc -n TagHelperSample -o TagHelperSample
现在,通过双击项目文件或在 VS 代码中,通过在已打开的控制台中键入以下命令,在 Visual Studio 中打开项目:
cd TagHelperSample
code .
本章中的所有代码示例都可以在本书的 GitHub 存储库中的中找到 https://github.com/PacktPublishing/Customizing-ASP.NET-Core-5.0/tree/main/Chapter10 。
I 生成 TagHelper
使用TagHelpers,您可以扩展现有的 HTML 标记或创建新的标记,这些标记将在服务器端呈现。扩展和新标记在浏览器中都不可见。TagHelpers是一种在服务器端编写更简单(更少)HTML 或 Razor 代码的快捷方式。TagHelpers将在服务器上解释,并为浏览器生成“真实”的 HTML 代码。
TagHelpers在 ASP.NET Core 中并不是什么新鲜事。自 ASP.NET Core 的第一个版本以来,它们就一直存在。大多数现有的和内置的TagHelpers都是老式 HTML 助手的替代品,它们仍然存在并在 ASP.NET Core 中工作,以保持 Razor 视图与 ASP.NET Core 兼容。
扩展 HTML 标记的一个非常基本的例子是内置的AnchorTagHelper:
<!-- old fashioned HtmlHelper -->
@Html.ActionLink("Home", "Index", "Home")
<!-- new TagHelper -->
<a asp-controller="Home" asp-action="Index">Home</a>
许多 HTML 开发人员发现在 HTML 标记之间有HtmlHelper有点奇怪。它很难阅读,而且在阅读代码时有点破坏性。对于习惯于阅读此类代码的 ASP.NET Core 开发人员来说,可能不是这样,但与TagHelpers相比,它真的很难看。TagHelpers感觉更自然,更像 HTML,即使它们不是,即使它们在服务器上呈现。
很多HtmlHelper实例可以替换为TagHelper。
还有一些新标签是用TagHelpers构建的,这些标签不是 HTML 格式的,但看起来像 HTML。一个例子是EnvironmentTagHelper:
<environment include="Development">
<link rel="stylesheet"
href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</environment>
<environment exclude="Development">
<link rel="stylesheet"
href="https://ajax.aspnetcdn.com/ajax/bootstrap/3.3.7/ css/bootstrap.min.css"
asp-fallback-href="~/lib/bootstrap/dist/css/ bootstrap.min.css"
asp-fallback-test-class="sr-only"
asp-fallback-test-property="position"
asp-fallback-test-value="absolute" />
<link rel="stylesheet"
href="~/css/site.min.css"
asp-append-version="true" />
</environment>
此TagHelper根据当前运行时环境呈现(或不呈现)内容。在这种情况下,目标环境就是开发模式。如果当前运行时环境设置为Development,则第一个环境标签呈现内容,如果未设置为Development,则第二个标签呈现内容。这使它成为在Development模式下呈现可调试脚本或样式以及在任何其他运行时环境中缩小和优化代码的有用助手。
现在让我们看看如何创建我们自己的定制TagHelpers。
创建自定义标记帮助程序
要使用我们将在本章中创建的所有自定义TagHelpers,您需要参考当前程序集来告诉框架在哪里可以找到TagHelpers。打开View/Shared/文件夹中的_ViewImports.cshtml文件,并在文件末尾添加以下行:
@addTagHelper *, TagHelperSample
下面是一个快速示例,演示如何使用TagHelper扩展现有标记:
-
Let's assume we need to have a tag configured as bold and colored in a specific color:
<p strong color="red">Use this area to provide additional information.</p>这看起来像是九十年代非常老式的 HTML,但这只是为了演示一个简单的
TagHelper。 -
The current method to do this task is to use a
TagHelperto extend any tag that has an attribute calledstrong, as shown in the following code snippet:[HtmlTargetElement(Attributes = "strong")] public class StrongTagHelper : TagHelper { public string Color { get; set; } public override void Process( TagHelperContext context, TagHelperOutput output) { output.Attributes.RemoveAll("strong"); output.Attributes.Add("style", "font-weight:bold;"); if (!String.IsNullOrWhiteSpace(Color)) { output.Attributes.RemoveAll("style"); output.Attributes.Add("style", $"font-weight:bold;color:{Color};"); } } }第一行告诉标记助手处理目标属性为
strong的标记。这个TagHelper没有定义自己的标签,但它提供了一个额外的属性来指定颜色。Process方法定义了如何将 HTML 呈现给输出流。在本例中,它将一些 CSS 内联样式添加到当前标记中。它还将从当前标记中删除目标属性。color属性不会显示。这将显示如下:
<p style="font-weight:bold;color:red;">Use this area to provide additional information.</p>
下一个示例显示如何使用TagHelper定义自定义标记:
-
让我们创建一个名为
GreeterTagHelper:public class GreeterTagHelper : TagHelper { [HtmlAttributeName("name")] public string Name { get; set; } public override void Process( TagHelperContext context, TagHelperOutput output) { output.TagName = "p"; output.Content.SetContent($"Hello {Name}"); } }的类
-
This
TagHelperhandles agreetertag that has a property name. In theProcessmethod, the current tag will be changed to aptag and the new content is set as the current output:<greeter name="Readers"></greeter>结果如下所示:
<p>Hello Readers</p>
但是如果你需要做一些更复杂的事情呢?让我们进一步探索呃。
检查更复杂的场景
最后一节中的TagHelpers非常基本,只是为了展示TagHelpers是如何工作的。下一个示例稍微复杂一点,显示了一个真实的场景。此TagHelper呈现一个包含项目列表的表。这是一个通用的TagHelper,显示了创建自己的自定义TagHelpers的真正原因。这样,您就可以重用一段独立的视图代码。例如,您可以包装引导组件,使其更易于使用一个标记,而不是嵌套五个级别的div标记。或者,您可以简化 Razor 视图:
-
Let's start by creating the
DataGridTagHelperclass. This next code snippet isn't complete, but we will complete theDataGridTagHelperclass in the following steps:public class DataGridTagHelper : TagHelper { [HtmlAttributeName("Items")] public IEnumerable<object> Items { get; set; } public override void Process( TagHelperContext context, TagHelperOutput output) { output.TagName = "table"; output.Attributes.Add("class", "table"); var props = GetItemProperties(); TableHeader(output, props); TableBody(output, props); } }在
Process方法中,我们调用私有子方法来执行实际工作,以使类更具可读性。您可能需要在文件开头添加以下
using语句:using System.Reflection; using System.ComponentModel; using System.ComponentModel; -
因为这是一个通用的
TagHelper,所以需要分析传入的对象。GetItemProperties方法获取属性项的类型,并从该类型加载PropertyInfos。PropertyInfos将用于获取表头和值:private PropertyInfo[] GetItemProperties() { var listType = Items.GetType(); Type itemType; if (listType.IsGenericType) { itemType = listType.GetGenericArguments() .First(); return itemType.GetProperties( BindingFlags.Public | BindingFlags.Instance); } return new PropertyInfo[] { }; } -
下面的代码片段显示了表头的生成。
TableHeader方法将所需的 HTML 标记直接写入TagHelperOutput。它还使用PropertyInfos列表获取将用作表头名称的属性名称:private void TableHeader( TagHelperOutput output, PropertyInfo[] props) { output.Content.AppendHtml("<thead>"); output.Content.AppendHtml("<tr>"); foreach (var prop in props) { var name = GetPropertyName(prop); output.Content.AppendHtml($"<th>{name}</th>"); } output.Content.AppendHtml("</tr>"); output.Content.AppendHtml("</thead>"); } -
将属性名称用作表标题名称并不总是有用的。这就是为什么
GetPropertyName方法也尝试从DisplayNameAttribute读取值,这是DataAnnotation的一部分,在 MVC 用户界面中显示的数据模型中大量使用。因此,支持此属性是有意义的:private string GetPropertyName( PropertyInfo property) { var attribute = property .GetCustomAttribute<DisplayNameAttribute>(); if (attribute != null) { return attribute.DisplayName; } return property.Name; } -
此外,还需要显示值。
TableBody方法完成该任务:private void TableBody( TagHelperOutput output, PropertyInfo[] props) { output.Content.AppendHtml("<tbody>"); foreach (var item in Items) { output.Content.AppendHtml("<tr>"); foreach (var prop in props) { var value = GetPropertyValue(prop, item); output.Content.AppendHtml( $"<td>{value}</td>"); } output.Content.AppendHtml("</tr>"); } output.Content.AppendHtml("</tbody>"); } -
要从实际对象获取值,使用
GetPropertyValue方法:private object GetPropertyValue( PropertyInfo property, object instance) { return property.GetValue(instance); } -
To use this
TagHelper, you just need to assign a list of items to this tag:<data-grid items="Model.Persons"></data-grid>在本例中,它是一个人员列表,我们在当前模型的
Persons属性中获得该列表。 -
The
Personclass we are using here looks like this:public class Person { [DisplayName("First name")] public string FirstName { get; set; } [DisplayName("Last name")] public string LastName { get; set; } public int Age { get; set; } [DisplayName("Email address")] public string EmailAddress { get; set; } }因此,并非所有属性都有
DisplayNameAttribute,因此需要GetPropertyName方法中的回退来获取实际的属性名称,而不是DisplayName值。 -
这个
TagHelper需要更多的检查和验证,然后才能在生产中使用,但它是有效的。显示使用 GenFu 生成的虚假数据列表(参见第 7 章、使用自定义 OutputFormatter进行内容协商,了解 GenFu):

图 10.2–TagHelper 示例正在运行
现在,您可以用更多的特性来扩展这个TagHelper,包括排序、过滤和分页。您可以在各种环境中自由尝试。
总结
TagHelpers在重用部分视图以及简化和清理视图时非常有用,如关于DataGridTagHelper的示例中所示。还可以为库提供有用的视图元素。在进一步阅读一节中,您可以尝试更多已有的TabHelper库和示例。
在下一章中,我们将讨论如何定制 ASP.NET Core web 应用的宿主。
进一步阅读
- Damian Edwards,TagHelperPack:https://github.com/DamianEdwards/TagHelperPack
- David Paquette,TagHelperSamples:https://github.com/dpaquette/TagHelperSamples
- Teleric 引导的 TagHelpers:https://www.red-gate.com/simple-talk/dotnet/asp-net/asp-net-core-tag-helpers-bootstrap/
- jQuery 的标记帮助程序:https://www.jqwidgets.com/asp.net-core-mvc-tag-helpers/
十一、配置WebHostBuilder
当阅读第 4 章使用 Kestrel配置和定制 HTTPS 时,您可能会问自己,如何使用用户机密将密码传递到 HTTPS 配置?您甚至可能想知道是否可以从Program.cs中获取配置。
在本章中,我们将通过以下主题回答这个问题:
- 复核
WebHostBuilderContext
本章中的主题涉及 ASP.NET Core 架构的主机层:

图 11.1–ASP.NET Core 体系结构
技术要求
要完成本章中的练习,您需要创建一个 ASP.NET Core 应用。打开控制台、shell 或 bash 终端,并切换到工作目录。使用以下命令创建新的 web 应用:
dotnet new web -n HostBuilderConfig -o HostBuilderConfig
现在,通过双击项目文件或在 VS 代码中,通过在已打开的控制台中键入以下命令,在 Visual Studio 中打开项目:
cd HostBuilderConfig
code .
本章中的所有代码示例都可以在本书的 GitHub 存储库中的中找到 https://github.com/PacktPublishing/Customizing-ASP.NET-Core-5.0/tree/main/Chapter11 。
R 电子考试 WebHostBuilderContext
还记得我们在第 4 章中看到的Program.cs中的WebHostBuilderContextKestrel 配置吗?用 Kestrel配置和定制 HTTPS?在那一章中,我们看到您应该使用用户机密来配置证书的密码,如下面的代码片段所示:
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
.UseKestrel(options =>
{
options.Listen(
IPAddress.Loopback,
5000);
options.Listen(
IPAddress.Loopback,
5001,
listenOptions =>
{
listenOptions.UseHttps(
"certificate.pfx",
"topsecret");
});
})
.UseStartup<Startup>();
});
}
}
通常,您无法获取此代码中的配置。您需要知道UseKestrel()方法是重载的,您可以在这里看到:
.UseKestrel((host, options) =>
{
// ...
})
第一个参数是WebHostBuilderContext类型,您可以使用它访问配置。因此,让我们稍微重写 lambda 以使用此上下文:
.UseKestrel((host, options) =>
{
var filename = host.Configuration.GetValue(
"AppSettings:certfilename", "");
var password = host.Configuration.GetValue(
"AppSettings:certpassword", "");
options.Listen(IPAddress.Loopback, 5000);
options.Listen(IPAddress.Loopback, 5001,
listenOptions =>
{
listenOptions.UseHttps(filename, password);
});
})
在本示例中,我们使用冒号分隔符写入键,因为这是您需要从appsettings.json读取嵌套配置的方式:
{
"AppSettings": {
"certfilename": "certificate.pfx",
"certpassword": "topsecret"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
重要提示
这只是如何读取配置以配置 Kestrel 的示例。请不要在代码中存储任何凭证。请使用以下概念。
您还可以从设置密钥的用户机密存储中读取,如下所示:
dotnet user-secrets init
dotnet user-secrets set "AppSettings:certfilename"
"certificate.pfx"
dotnet user-secrets set "AppSettings:certpassword"
"topsecret"
这也适用于环境变量:
SET APPSETTINGS_CERTFILENAME=certificate.pfx
SET APPSETTINGS_CERTPASSWORD=topsecret
重要提示
由于用户机密存储仅用于本地开发,因此应通过环境变量将凭据传递给生产中的应用或类似生产的应用。此外,您将在 Azure 中设置的应用设置配置将作为环境变量传递给您的应用。
那么,这一切是如何运作的呢?
它是如何工作的?
你还记得以前你需要在Startup.csASP.NET Core 1.0 中配置应用配置的日子吗?是在 startup 类的构造函数中配置的,如果您添加了用户机密,看起来与此类似:
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json")
.AddJsonFile($"appsettings.{env.EnvironmentName}.json",
optional: true);
if (env.IsDevelopment())
{
builder.AddUserSecrets();
}
builder.AddEnvironmentVariables();
Configuration = builder.Build();
这段代码现在被包装在CreateDefaultBuilder方法中(如您在 GitHub 上看到的;有关详细信息,请参阅进一步阅读部分),如下所示:
builder.ConfigureAppConfiguration((hostingContext, config)
=>
{
var env = hostingContext.HostingEnvironment;
config
.AddJsonFile(
"appsettings.json",
optional: true,
reloadOnChange: true)
.AddJsonFile(
$"appsettings.{env.EnvironmentName}.json",
optional: true,
reloadOnChange: true);
if (env.IsDevelopment())
{
var appAssembly = Assembly.Load(
new AssemblyName(env.ApplicationName));
if (appAssembly != null)
{
config.AddUserSecrets(appAssembly,
optional: true);
}
}
config.AddEnvironmentVariables();
if (args != null)
{
config.AddCommandLine(args);
}
})
这几乎是相同的代码,并且是构建WebHost.cs文件时首先执行的事情之一。
它必须是第一件事之一,因为 Kestrel 可以通过应用配置进行配置。您可以使用环境变量或appsettings.json指定端口和 URL 等。
您可以在WebHost.cs中找到这些行:
builder.UseKestrel((builderContext, options) =>
{
options.Configure(
builderContext.Configuration.GetSection("Kestrel"));
})
这意味着您可以将这些行添加到appsettings.json以配置 Kestrel 端点:
"Kestrel": {
"EndPoints": {
"Http": {
"Url": "http://localhost:5555"
}
}
}
或者,可以使用以下环境变量来配置端点:
SET KESTREL_ENDPOINTS_HTTP_URL=http://localhost:5555
现在,让我们回顾一下本章所涵盖的所有内容。
总结
在Program.cs内,您可以在配置方法的 lambda 内使用 app 配置,前提是您可以访问WebHostBuilderContext。这样,您就可以使用您想要配置的所有配置WebHostBuilder。
在下一章中,我们将了解托管的详细信息。您将了解不同的托管模型以及如何以不同的方式托管 ASP.NET Core 应用。
十二、使用不同的托管模式
在本章中,我们将讨论如何在 ASP.NET Core 中自定义托管。我们将研究托管选项、不同类型的托管,并快速查看 IIS 上的托管。本章只是一个概述。关于每一个主题,我们可以进行更详细的讨论,但这将独自填满一本完整的书!
在本章中,我们将介绍以下主题:
- 设置
WebHostBuilder - 设置 Kestrel
- 设置 HTTP.sys
- 在 IIS 上托管
- 在 Linux 上使用 NGINX 或 Apache
本章中的主题涉及 ASP.NET Core 架构的主机层:

图 12.1–ASP.NET Core 体系结构
本章讨论服务器体系结构的以下主题:

图 12.2–ASP.NET 服务器体系结构
技术要求
对于 r 本系列,我们只需要设置一个小的空 web 应用:
dotnet new web -n ExploreHosting -o ExploreHosting
就这样。使用 Visual Studio 代码打开它:
cd ExploreHosting
code .
瞧!一个简单的项目将在 VS 代码中打开。
本章的代码可以在 GitHub 上找到:https://github.com/PacktPublishing/Customizing-ASP.NET-Core-5.0/tree/main/Chapter12 。
Se 安装 WebHostBuilder
与上一章一样,我们将在本节中重点讨论Program.cs。WebHostBuilder是我们的朋友。这里是我们配置和创建 web 主机的地方。
下面的代码片段是我们在 Visual Studio 中使用文件新建项目或使用.NET CLI 运行dotnet new命令创建的每个新 ASP.NET Core web 的默认配置:
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateHostBuilder(
string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
正如我们在前面的章节中已经知道的,默认构建已经预先配置了所有必要的内容。已为您配置在 Azure 或本地 IIS 上成功运行应用所需的全部资源。
但是您可以覆盖几乎所有这些默认配置,包括主机配置。
接下来,让我们设置 Kestrel。
设置 Kestrel
创建WebHostBuilder后,我们可以使用各种功能来配置生成器。这里我们已经看到其中一个,它指定了应该使用的启动类。
笔记
正如第 4 章中所述,使用 Kestrel配置和定制 HTTPS,Kestrel 是托管应用的一种可能性。Kestrel 是一个内置于.NET 的 web 服务器,基于.NET 套接字实现。以前,它是在libuv之上构建的,而libuv是 Node.js 使用的同一个 web 服务器。Microsoft 删除了对libuv的依赖,并基于.NET 套接字创建了自己的 web 服务器实现。
在上一章中,我们看到了配置 Kestrel 选项的UseKestrel方法:
.UseKestrel((host, options) =>
{
// ...
})
第一个参数是访问已配置主机设置或配置本身的WebHostBuilderContext类型。第二个参数是配置 Kestrel 的对象。此代码段显示了我们在上一章中为配置主机需要侦听的套接字端点所做的操作:
.UseKestrel((host, options) =>
{
var filename = host.Configuration.GetValue(
"AppSettings:certfilename", "");
var password = host.Configuration.GetValue(
"AppSettings:certpassword", "");
options.Listen(IPAddress.Loopback, 5000);
options.Listen(IPAddress.Loopback, 5001,
listenOptions =>
{
listenOptions.UseHttps(filename, password);
});
})
这将覆盖您可以传入 URL 的默认配置;例如,使用launchSettings.json的applicationUrl属性或环境变量。
现在让我们看看如何设置HTTP.sys。
设置 HTTP.sys
还有另一种托管选择;一个不同的 web 服务器实现。HTTP.sys是一个相当成熟的库,深入 Windows,可用于托管 ASP.NET Core 应用:
.UseHttpSys(options =>
{
// ...
})
HTTP.sys与 Kestrel 不同。它不能在 IIS 中使用,因为它与 IIS 的 ASP.NET Core 模块不兼容。
使用HTTP.sys代替 Kestrel 的的主要原因是Windows 身份验证,不能在 Kestrel 中使用。如果您需要在没有 IIS 的情况下将应用公开到 internet,也可以使用HTTP.sys。
笔记
IIS 已经在HTTP.sys之上运行多年。这意味着UseHttpSys()和 IIS 使用相同的 web 服务器实现。欲了解更多关于HTTP.sys的信息,请阅读文档,相关链接可在进一步阅读部分找到。
接下来,让我们看看如何使用 IIS 进行托管。
在 IIS 上托管
ASP.NET Core 应用不应直接暴露于互联网,即使它支持 Kestrel 或HTTP.sys。最好在两者之间有一个反向代理,或者至少有一个监视托管过程的服务。对于 ASP.NET Core,IIS isn 不仅仅是一个反向代理。它还负责托管进程,以防它因错误而中断。如果发生这种情况,IIS 将重新启动进程。NGINX 可以用作 Linux 上的反向代理,它还负责托管进程。
要在 IIS 或 Azure 上托管 ASP.NET Core web,您需要先发布它。发布不仅仅是编译项目;它还准备在 IIS、Azure 或 Linux 上的 web 服务器(如 NGINX)上托管项目。
以下命令将发布项目:
dotnet publish -o ..\published -r win-x64
在系统浏览器中查看时,应如下所示:

图 12.3–一个.NET 已发布文件夹
这将生成可在 IIS 中映射的输出。它还创建一个web.config文件来添加 IIS 或 Azure 的设置。它以 DLL 的形式包含已编译的 web 应用。
如果发布自包含的应用,它还包含运行时本身。一个自包含的应用带来了自己的.NET Core 运行时,但是交付的大小增加了很多。
在 IIS 中呢?只需在 IIS 中创建一个新网站,并将其映射到您放置已发布输出 ut 的文件夹:

图 12.4–.NET 发布对话框
如果您需要更改安全性,如果您有一些数据库连接,等等,那么它会变得更复杂一些。这可能是单独一章的主题。不过,这里有一个快速版本:

图 12.5–你好,世界!在浏览器中查看
图 12.5是演示项目startup.cs中小型中间件的输出:
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
接下来,我们将讨论 Linux 的一些替代方案。
在 Linux 上使用 NGINX 或 Apache
在 Linux 上发布 ASP.NET Core 应用与在 IIS 上发布 ASP.NET Core 应用的方式非常相似,但为反向代理准备 ASP.NET Core 应用需要一些额外的步骤。您将需要一个 web 服务器,如NGINX或Apache作为反向代理服务器,将流量转发给 Kestrel 和 ASP.NET Core 应用:
-
First, you need to allow your app to accept two specific forwarded headers. To do this, open the
Startup.csfile and add the following lines to theConfiguremethod before theUseAuthenticationmiddleware:app.UseForwardedHeaders(new ForwardedHeadersOptions { ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto });您还需要信任来自反向代理的传入流量。这需要您在
ConfigureServices方法中添加以下行:services.Configure<ForwardedHeadersOptions>(options => { options.KnownProxies.Add( IPAddress.Parse("10.0.0.100")); });在此处添加代理的 IP 地址。这只是一个样本。
-
Then, you need to publish the application:
dotnet publish --configuration Release将生成输出复制到名为
/var/www/yourapplication的文件夹中。您还应该通过调用以下命令在 Linux 上进行快速测试:dotnet <yourapplication.dll>这里,
yourapplication.dll是已编译的应用,包括路径。如果一切正常,您应该能够通过http://localhost:5000/呼叫您的网站。 -
If it is working, the application should run as a service. This requires you to create a service file on
/etc/systemd/system/. Call the filekestrel-yourapplication.serviceand place the following content in it:[Unit] Description=Example .NET Web API App running on Ubuntu [Service] WorkingDirectory=/var/www/yourapplication ExecStart=/usr/bin/dotnet /var/www/yourapplication/yourapplication.dll Restart=always # Restart service after 10 seconds if the dotnet service # crashes: RestartSec=10 KillSignal=SIGINT SyslogIdentifier=dotnet-example User=www-data Environment=ASPNETCORE_ENVIRONMENT=Production Environment=DOTNET_PRINT_TELEMETRY_MESSAGE=false [Install] WantedBy=multi-user.target确保第 5 行和第 6 行中的路径指向放置生成输出的文件夹。此文件定义应用应作为服务在默认端口上运行。它还监视应用并在其崩溃时重新启动。它还定义了用于配置应用的环境变量。参见第 1 章定制日志,了解如何使用环境变量配置应用。
接下来,我们将了解如何配置 NGINX。
配置 NGINX
现在您可以使用以下代码告诉 NGINX 要做什么:
server {
listen 80;
server_name example.com *.example.com;
location / {
proxy_pass http://localhost:5000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection keep-alive;
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_set_header X-Forwarded-For
$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
这会告诉 NGINX 将端口80上的呼叫转发到example.com,并将其子域转发到http://localhost:5000,后者是您应用的默认地址。
配置 Apache
Apache 配置看起来非常类似于 NGINX 方法,并且在最后做了相同的事情:
<VirtualHost *:*>
RequestHeader set "X-Forwarded-Proto" expr=%{REQUEST_ SCHEME}
</VirtualHost>
<VirtualHost *:80>
ProxyPreserveHost On
ProxyPass / http://127.0.0.1:5000/
ProxyPassReverse / http://127.0.0.1:5000/
ServerName www.example.com
ServerAlias *.example.com
ErrorLog ${APACHE_LOG_DIR}yourapplication-error.log
CustomLog ${APACHE_LOG_DIR}yourapplication-access.log
common
</VirtualHost>
NGINX 和 Apache 就是这样。现在让我们来总结一下这一章。
总结
ASP.NET Core 和.NET CLI 已经包含了在各种平台上启动和运行的所有工具,以及为 Azure 和 IIS 以及 NGINX 做好准备的所有工具。这非常简单,文档中对此进行了详细描述。
目前,我们有WebHostBuilder来创建应用的托管环境。在版本 3.0 中,我们有HostBuilder,它能够创建一个完全独立于任何 web 上下文的托管环境。
在本书的最后一章中,我们将研究新的端点路由,它允许您以简单灵活的方式创建自己的托管端点。
进一步阅读
- Kestrel 文献:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel
- HTTP.sys 文档:https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/httpsys?view=aspnetcore-5.0
- ASP.NET Core:https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/aspnet-core-module?view=aspnetcore-2.2
十三、处理端点路由
在本章中,我们将讨论 ASP.NET Core 中的新端点路由。我们将学习什么是端点路由,它是如何工作的,在哪里使用它,以及如何创建到自己端点的路由。
在本章中,我们将介绍以下主题:
- 探索端点路由
- 创建自定义端点
- 创建更复杂的端点
本章中的主题涉及 ASP.NET Core 架构的路由层:

图 13.1–ASP.NET Core 体系结构
技术要求
对于本系列,我们只需要设置一个小的空 web 应用:
dotnet new mvc -n RoutingSample -o RoutingSample
就这样!使用 Visual Studio 代码打开它:
cd RoutingSample
code .
本章中的所有代码示例都可以在本书的 GitHub 存储库中的中找到 https://github.com/PacktPublishing/Customizing-ASP.NET-Core-5.0/tree/main/Chapter13 。
E 探索端点路由
要了解端点路由,您需要了解什么是端点,什么是路由。
端点是当路由将传入请求映射到应用时执行的应用部分。让我们更详细地分析一下这个定义。
客户机通常从服务器请求资源。在大多数情况下,客户端是浏览器。资源由指向特定目标的 URL 定义。在大多数情况下,目标是一个网页。它也可以是一个移动应用,从 JSON Web API 请求特定数据。应用请求的数据在 URL 中定义。
这意味着传入请求也由 URL 定义。另一方面,执行端点映射到特定路由。路由是 URL 或 URL 的模式。ASP.NET Core 开发人员已经熟悉这种路由模式:
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
如果路由或路由模式与传入请求的 URL 匹配,则请求将映射到该端点。在这种情况下,请求被映射到 MVC 端点。
ASP.NET Core 可以映射到以下端点:
- 控制器(MVC、Web API)
- 剃须刀页面
- 信号器(和 Blazor 服务器)
- gRPC 服务
- 健康检查
大多数端点都有非常简单的路由模式。只有 MVC 和 WebAPI 使用更复杂的模式。Razor 页面的路由定义基于实际页面的文件夹和文件结构。
在 ASP.NET Core 2.2 引入端点之前,路由只是 MVC 和 Web API 中的一件事。Razor 页面中的隐式路由是在那里构建的,而 Signaler 还没有真正准备好。Blazor 和 gRPC 在当时不是什么东西,健康检查最初是作为中间件实现的。
引入端点路由是为了将路由与实际端点分开。这使得框架更加灵活,新的端点不需要实现它们自己的路由。通过这种方式,端点可以使用现有的灵活路由技术来映射到特定的路由。
接下来,我们将了解如何创建自己的自定义端点。
创建自定义端点
创建端点的最简单方法是使用基于 lambda 的端点:
endpoints.Map("/map", async context =>
{
await context.Response.WriteAsync("OK");
});
这将/map路由映射到一个简单的端点,该端点将"OK"一词写入响应流。
您可能需要将Microsoft.AspNetCore.Http名称空间添加到using语句中。
您还可以将特定的 HTTP 方法(例如GET、POST、PUT和DELETE映射到端点。下面的代码显示了如何映射GET和POST方法:
endpoints.MapGet("/mapget", async context =>
{
await context.Response.WriteAsync("Map GET");
});
endpoints.MapPost("/mappost", async context =>
{
await context.Response.WriteAsync("Map POST");
});
我们还可以将两个或多个 HTTP 方法映射到一个端点:
endpoints.MapMethods(
"/mapmethods",
new[] { "DELETE", "PUT" },
async context =>
{
await context.Response.WriteAsync("Map Methods");
});
这些端点看起来像我们在第 6 章编写自定义中间件中看到的基于 lambda 的终止中间件。这些是终止管道并返回结果的中间件,例如基于 HTML 的视图、JSON 结构化数据或类似的。端点路由是一种更灵活的创建输出的方法,应该在 ASP.NET Core 3.0 以后的所有版本中使用。
使用路由而不是使用app.Map的经典方式有什么好处?
在第 6 章编写定制中间件中,我们看到我们可以这样分支管道:
app.Map("/map1", mapped =>
{
// some more Middlewares
});
这也会创建一个路由,但它只会侦听以/map开头的 URL。如果您希望有一个处理模式(如/map/{id:int?})的路由引擎来匹配/map/456而不是/map/abc,那么您应该使用新的路由,如本节前面所示。
这些基于 lambda 的端点仅对简单场景有用。因为它们是在Startup.cs中定义的,如果您开始实施更复杂的场景,事情会很快变得一团糟。
我们应该尝试找到一种更结构化的方法来创建自定义端点。
C 创建更复杂的终点
在本节中,我们将逐步创建更复杂的端点。让我们通过编写一个非常简单的运行状况检查端点来实现这一点,如果您要在 Kubernetes 群集中运行应用,或者只是告诉其他人您的运行状况,您可能需要这样做:
-
Microsoft proposes starting with the definition of the API to add the endpoint from the developer point of view. We do the same here. This means that we will add a
MapSomethingmethod first, without an actual implementation. This will be an extension method onIEndpointRouteBuilder. We are going to call itMapMyHealthChecks:// the new endpoint endpoints.MapMyHealthChecks("/myhealth"); endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");新端点应该以与预构建端点相同的方式添加,以免混淆需要使用它的任何开发人员。
现在我们知道了该方法的外观,让我们来实现它。
-
We need to create a new static class called
MyHealthChecksExtensionsand place an extension method insideMapMyHealthChecks, which extendsIEndpointRouteBuilderand returnsIEndpointConventionBuilder:public static class MapMyHealthChecksExtensions { public static IEndpointConventionBuilder MapMyHealthChecks ( this IEndpointRouteBuilder endpoints, string pattern = "/myhealth") { // ... } }这只是骨架。让我们先从实际端点开始,然后再使用它。
-
The actual endpoint will be written as a terminating middleware, which is a middleware that doesn't call the next one (see Chapter 6, Writing Custom Middleware) and creates an output to the response stream:
public class MyHealthChecksMiddleware { private readonly ILogger<MyHealthChecksMiddleware> _logger; public MyHealthChecksMiddleware ( RequestDelegate next, ILogger<MyHealthChecksMiddleware> logger) { _logger = logger; } public async Task Invoke(HttpContext context) { // add some checks here... context.Response.StatusCode = 200; context.Response.ContentType = "text/plain"; await context.Response.WriteAsync("OK"); } }实际工作采用
Invoke方法完成。目前,这只不过是用明文OK和 HTTP 状态200进行响应,如果您只想显示您的应用正在运行,这也没关系。可以通过实际检查来扩展该方法,例如检查数据库或相关服务的可用性。然后需要更改 HTTP 状态以及与检查结果相关的输出。让我们使用这个终止中间件。
-
让我们回到
MapMyHealthChecks方法的框架。我们现在需要创建自己的管道,将其映射到给定的路线。在该方法中放置以下行:var pipeline = endpoints .CreateApplicationBuilder() .UseMiddleware<MyHealthChecksMiddleware>() .Build(); return endpoints.Map(pattern, pipeline) .WithDisplayName("My custom health checks"); -
这种方法允许您为这个新管道添加更多的中间件。
WithDisplayName扩展方法将配置的显示名称设置为端点。 -
就这样!在 IDE 中按F5启动应用,并在浏览器中调用
https://localhost:5001/myhealth。您应该在浏览器中看到OK:

图 13.2–端点路由输出的屏幕截图
您还可以将现有的 te 终端中间件转换为路由端点,以受益于更灵活的路由。
这一章就到此为止!
Summary
ASP.NET Core 知道处理请求和向请求客户端提供信息的许多方法。端点路由是一种基于请求的 URL 和请求的方法提供资源的方法。
在本章中,您学习了如何使用终止中间件作为端点,将其映射到新的路由引擎,以便更灵活地匹配您希望通过其向请求客户端提供信息的路由。
这一章是本书的最后一章。我希望定制 ASP.NET Core 框架的十三个方案和配方将帮助您改进应用,并从 ASP.NET Core 中获得更多。
最后,我还要说谢谢你读了这本书!




浙公网安备 33010602011771号