使用 ASP.NET Core 自定义中间件创建 HTTP 请求管道:可在 Mac、Windows、Linux 或 Docker 容器上构建/运行






4.99/5 (22投票s)
如何在 ASP.NET Core 上构建 HTTP 请求管道
引言
正如 I/O 是计算机交换信息的手段一样,万维网使用请求-响应模式进行通信。HTTP 是客户端-服务器在 Web 上进行请求-响应通信的协议。在本文中,我们将使用 ASP.NET Core 上的中间件组件构建一个管道来处理 HTTP 请求。在 ASP.NET Core 上构建 HTTP 请求管道的一个巨大优势是,您可以创建一个精简且模块化的管道,该管道可以在 Windows、Linux 和 Mac 上运行。ASP.NET Core 是开源的,有许多 NuGet 包可以帮助您构建自定义管道以满足您的需求。您也可以编写自己的自定义中间件。ASP.NET Core 内置支持 依赖注入 (DI),这是一种用于实现应用程序松耦合的技术。
我们构建的 HTTP 请求管道可用于开发在 Docker 容器中运行的自定义微服务。它非常适合小型独立任务,但由于其模块化特性,可以扩展更多功能。
背景
正如老话所说,如果你不知道过去,你就不知道未来。那么,在 Microsoft IIS Web 服务器上运行的经典 ASP.NET 页面中的 HTTP 请求是如何流动的呢?在集成方法中,IIS 和 ASP.NET 请求管道将结合起来,通过原生和托管模块处理请求。下面来自 IIS.NET 的图表说明了 IIS 7 中 HTTP 请求的流程。
参考: https://www.iis.net/learn/get-started/introduction-to-iis/introduction-to-iis-architecture
在经典 ASP.NET 中,HTTP 请求会经过 HTTP 应用程序管道的多个事件。开发人员可以编写代码在事件引发时运行。他们还可以使用 IHttpModule 接口创建自定义模块,并将其添加到应用程序的 Web.config 文件的配置部分。
相比之下,ASP.NET Core 的设计旨在提高性能并最小化占用空间。您从一个干净的状态开始,然后将所需的功能添加到请求管道中,使用中间件。本文将介绍如何添加中间件来构建自定义 HTTP 请求管道。
必备组件
如果您打算 mengikuti 教程,您将需要安装以下项目
- 安装 .NET Core SDK。
- 我们将使用命令行并从头开始,以便更好地理解。起初,我建议使用您选择的文本编辑器。稍后,您可以在 Visual Studio Code 或 Visual Studio 2015 中打开项目,以利用 IDE 的优势。
开始吧
在本节中,我们将创建一个工作应用程序目录 DemoApp,并使用命令行创建一个新应用程序。我们还将更新项目文件并使用命令行安装所需的包。
创建应用程序目录并构建新项目
首先,请检查 .NET Core 是否已安装。创建一个目录来保存您的应用程序,并将其设为您的工作目录。在我的机器上,工作目录位于 C:\Projects\DemoApp。打开命令提示符,然后将目录更改为您的工作目录。使用 dotnet new
命令创建一个新应用程序。
dotnet new
上面的屏幕截图显示了 dotnet 版本、工作目录以及新 C# 项目中的两个文件。
我们的项目从最基本开始。由于我们的应用程序是基于 AspNet Core 构建的,因此我们需要在 project.json 文件中添加所需的依赖项。打开 project.json 文件,将 "Microsoft.AspNetCore.Server.Kestrel
": "1.1.0
" 添加为依赖项之一。
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable",
"emitEntryPoint": true
},
"dependencies": {
"Microsoft.AspNetCore.Server.Kestrel": "1.1.0"
},
"frameworks": {
"netcoreapp1.1": {
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.1.0"
}
},
"imports": "dnxcore50"
}
}
我们当前的应用程序是一个简单的控制台应用程序,带有一个 Program.cs 文件和一个 Main
方法,该方法输出到 Console
。我们将更改 Program.cs 文件以创建一个主机,该主机将使用 Kestrel Web 服务器和 Startup
类来运行我们的应用程序。用以下代码替换 Program.cs 中的代码
using System;
using Microsoft.AspNetCore.Hosting;
namespace DemoApp
{
public class Program
{
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseUrls("http://*:5000")
.UseKestrel()
.UseStartup<Startup>()
.Build();
host.Run();
}
}
}
在经典 ASP.NET 中,应用程序生命周期中会调用 Global.asax(继承自 HttpApplication
)中的 Application_Start
和 Application_End
事件。在 ASP.NET Core 中,应用程序在 Program.cs 的 Main
方法中进行初始化。我们代码中的 Main
方法会创建一个 WebHostBuilder
实例,并使用各种扩展方法来指定 URL、服务器和 Startup
类来构建主机并运行应用程序。Startup
类(我们将在下一步创建)是我们 HTTP 请求管道的入口点。
在 ASP.NET Core 中,我们使用约定进行编程。创建一个新的 Startup.cs 文件并添加以下代码。Startup 构造函数可以选择接受依赖项,例如 IHostingEnvironment
,它将通过依赖注入提供。
可选的 ConfigureServices
方法用于定义和配置应用程序可用的服务。ConfigureServices
只能接受 IServiceCollection
类型的参数。
Configure
方法用于使用中间件构建管道以及如何处理请求响应。Configure
方法必须接受 IApplicationBuilder
类型参数。类型为 IApplicationBuilder
、IHostingEnvironment
和 ILoggerFactory
的服务可以作为参数传递,如果可用,它们将被注入。
using System;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
namespace DemoApp
{
public class Startup
{
// Use this method to add framework services (MVC, EF, Identity, Logging)
// and application services
public void ConfigureServices(IServiceCollection services)
{
}
// Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app)
{
app.Run(context =>
{
return context.Response.WriteAsync("Hello from ASP.NET Core!");
});
}
}
}
让我们使用以下命令恢复 Project.json 中指定的依赖项
dotnet restore
这将创建一个新的 project.lock.json 文件,其中包含应用程序使用的所有 NuGet 包的列表。
我们现在将使用以下命令运行项目。这将编译并执行我们的项目。
dotnet run
如果您的项目成功编译,请打开浏览器并访问 https://:5000/。
恭喜您,您已成功在 ASP.NET Core 上构建了一个简单的 HTTP 应用程序。
自定义中间件
中间件是处理 HTTP 请求管道中的请求和响应的组件。这些组件链接在一起构成一个管道。RequestDelegate
用于链接中间件。什么是 RequestDelegate
?RequestDelegate
是一个接受 HttpContext
类型并返回 Task
类型(一个 Promise)的函数。
管道中的每个中间件都会调用序列中的下一个中间件或终止请求。您可以在调用下一个中间件之前和之后执行操作。让我们在 Startup.cs 文件的 Configure
方法中向 HTTP 请求管道添加一个内联演示中间件。
public void Configure(IApplicationBuilder app)
{
// add inline demo middleware
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Hello from inline demo middleware...");
// invoke the next middleware
await next.Invoke();
});
app.Run(async (context) =>
{
await context.Response.WriteAsync("Welcome to ASP.NET Core!");
});
}
在上面的代码中,内联演示中间件代码已通过 app.Use
注册。请注意,如果您不调用 next.Invoke()
,它将截断请求管道。另外请注意,在 app.Run
方法中,您有一个终端中间件,它在 HTTP 请求管道的末尾被调用。我已更改文本以区分消息。
在我们的内联演示中间件中,我们将 HttpContext
和 RequestDelegate
传递给 lambda 表达式,但对于复杂的中间件处理,您应该创建自己的类。以下是 DemoMiddleware
类的代码。它有一个接受 RequestDelegate
的构造函数和一个接受 HttpContext
的 Invoke
方法。
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
namespace DemoApp
{
public class DemoMiddleware
{
private readonly RequestDelegate _next;
public DemoMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
await context.Response.WriteAsync("Message from DemoMiddleware...");
await _next(context);
}
}
}
为了注册 DemoMiddleware
,我们将创建一个扩展 IApplicationBuilder
的类,以提供一种将中间件添加到请求管道的简便方法。以下是 DemoMiddlewareExtensions.cs 的代码。
using Microsoft.AspNetCore.Builder;
namespace DemoApp
{
public static class DemoMiddlewareExtensions
{
public static void UseDemoMiddleware(this IApplicationBuilder builder)
{
builder.UseMiddleware<DemoMiddleware>();
}
}
}
现在,通过在 Startup
类的 Configure
方法中调用 app.UseDemoMiddleware()
,我们可以将 DemoMiddleware
添加到 HTTP 请求管道。
public void Configure(IApplicationBuilder app)
{
// inline demo middleware
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Hello from inline demo middleware...");
// invoke the next middleware
await next.Invoke();
});
// standalone DemoMiddleWare;
app.UseDemoMiddleware();
// terminal middleware
app.Run(async (context) =>
{
await context.Response.WriteAsync("Welcome to ASP.NET Core!...");
});
}
在成功编译并执行我们的 DemoApp
项目(使用 dotnet run
)并在浏览器中访问 https://:5000/ 后,您应该会在 Startup
类的 Configure
方法中看到来自内联中间件、独立中间件和终端中间件添加到 HTTP 请求管道的消息。
请注意,HTTP 请求的处理顺序与中间件组件的序列相同,而响应的处理顺序则相反。理解 HTTP 管道中中间件的业务逻辑和链接顺序非常重要。中间件的顺序可能会对应用程序的安全性、性能和行为产生重大影响。
依赖注入和策略设计模式
ASP.NET Core 框架旨在支持依赖注入。它有一个内置的 控制反转 (IoC) 容器,也称为 DI 容器。内置的 DI 容器提供了负责提供 Startup
类 ConfigureServices
方法中配置的类型的实例的服务。
如果我们希望我们的 DemoMiddleware
类提供自定义消息,我们可以使用 策略设计模式。我们将创建一个 Interface
并将此接口的实现作为参数提供。您可以继续使用您喜欢的文本编辑器或 IDE。要将 DemoApp
项目打开到 Visual Studio 2015 中,请转到 **文件** 菜单,选择 **打开** > **项目/解决方案**。在“打开项目”对话框中,选择 DemoApp 文件夹中的 project.json。要将 Demo 项目打开到 Visual Studio Code 中,请转到 **文件** 菜单,选择 **打开**,然后打开 DemoApp 文件夹。这是 DemoApp
项目首次在 Mac 上的 Visual Studio Code 中打开时的屏幕截图。
以下是我们接口 IMessage.cs 的代码
namespace DemoApp
{
public interface IMesssage
{
string Info();
}
}
我们将在 RuntimeMessage
类之一中实现上述接口,以提供有关 DemoApp
运行的操作系统和框架的信息。以下是 RuntimeMessage.cs 的代码
using System.Runtime.InteropServices;
namespace DemoApp
{
public class RuntimeMessage : IMesssage
{
public string Info()
{
return $@"
OS Description: {RuntimeInformation.OSDescription}
OS Architecture: {RuntimeInformation.OSArchitecture}
Framework: {RuntimeInformation.FrameworkDescription}
Process Architecture: {RuntimeInformation.ProcessArchitecture}";
}
}
}
在 DemoMiddleware
类中,我们将更新构造函数以接受 IMessage
作为参数,并更新 Invoke
方法以写入从上面的 Info
方法返回的响应。更新后的 DemoMiddleware.cs 如下
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
namespace DemoApp
{
public class DemoMiddleware
{
private readonly RequestDelegate _next;
private readonly IMesssage _message;
public DemoMiddleware(RequestDelegate next, IMesssage message)
{
_next = next;
_message = message;
}
public async Task Invoke(HttpContext context)
{
await context.Response.WriteAsync("\r\nMessage from DemoMiddleware:");
await context.Response.WriteAsync(_message.Info() + "\r\n");
await _next(context);
}
}
}
为了将 IMessage
解析为 RuntimeMessage
,我们将在 Startup
类的 ConfigureServices
方法中注册我们的依赖项。AddTransient
方法用于在每次请求时将抽象类型映射到具体类型(ASP.NET 的容器将它管理的类型称为服务)。
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IMesssage, RuntimeMessage>();
}
让我们编译并运行代码。您可以在命令行中使用 dotnet run
,或在 Visual Studio 工具中按 **F5** 进行调试。以下屏幕截图是我使用 Visual Studio Code 在 Mac 上调试代码时的截图。
在 Mac、Windows、Linux 或 Docker 容器上构建和运行应用程序
来自 DemoMiddleware
的运行时信息消息将基于 DemoApp
运行的平台。我获得的上述运行时信息消息是我使用 Mac OS X El Capitan 运行 DemoApp
时获得的消息。
ASP.NET Core 可以在 Windows、Linux 和 Mac 上运行,您可以选择在哪里运行应用程序。在 Windows 10 计算机上,Chrome 浏览器中的运行时信息消息如下所示
下面的图片是我在 Docker 容器中运行应用程序时的截图。
Docker 是构建和运行微服务的绝佳平台。安装 Docker、构建 Docker 映像、发布和在 Docker 容器中运行应用程序本身就是独立的课题。在这些主题上有一些优秀的文档,可在 https://docs.dockerd.com.cn/ 和 https://www.microsoft.com/net/core#dockercmd 上找到。如果您已经在机器上运行 Docker,您可以将以下代码复制到 DemoApp 文件夹中的 Dockerfile 中,并使用它来构建您的 Docker 映像。
FROM microsoft/dotnet:1.1.0-sdk-projectjson
COPY . /demoapp
WORKDIR /demoapp
EXPOSE 5000
ENV ASPNETCORE_URLS http://+:5000
RUN dotnet restore
RUN dotnet build
ENTRYPOINT ["dotnet", "run"]
您可以从 DemoApp 工作目录在命令行中构建 Docker 映像。
docker build . -t np:demoapp
当 Docker 映像成功构建后,使用以下命令启动 Docker 容器。
docker run -d -p 80:5000 -t np:demoapp
您可以使用以下命令查看所有容器的列表
docker ps -a
有关更多 Docker 命令,请参阅 https://docs.dockerd.com.cn/engine/reference/commandline/。以下是命令行中构建 demoapp 的 Docker 映像的屏幕截图
内置中间件
ASP.NET Core 1.1 附带了内置中间件,例如身份验证、CORS、路由、会话、静态文件、诊断、URL 重写、响应缓存和响应压缩。在 nugget 包中也有许多中间件可用。
在我们的 DemoApp
中,我们将添加诊断中间件用于异常处理,静态文件中间件用于从 www 文件夹提供内容,以及响应压缩中间件用于 GZip 内容以实现更快的网络传输。在添加中间件时,我们遵循以下步骤
- 将包依赖项添加到 project.json。
- 如果需要,在 Startup.cs 的
ConfigureServices
方法中为中间件添加服务。 - 在 Startup.cs 的
Configure
方法中将中间件添加到 HTTP 请求管道。 - 让我们将以下依赖项添加到 project.json。
"Microsoft.AspNetCore.Diagnostics": "1.1.0",
"Microsoft.AspNetCore.ResponseCompression": "1.0.0",
"Microsoft.AspNetCore.StaticFiles": "1.1.0"
从包名称可以明显看出我们可以为 DemoApp
提供哪些功能。您可以使用 CLI 运行 dotnet restore
,或使用 Visual Studio Code 或 Visual Studio 恢复包。ASP.NET Core 是跨平台的,您可以在 Mac、Linux 或 Windows 上开发应用程序。您可以使用您喜欢的工具,我将打开 Project.json 中的 DemoApp
项目。一旦我在 Project.json 中添加了上述依赖项并保存了文件,Visual Studio 就会恢复包,如下所示
由于 ASP.NET Core 具有内置的依赖注入,我们可以通过在 Startup
类的构造函数中传递适当的接口作为参数来注入服务(由 DI 容器管理的类型)。您还可以用 Autofac、NInject 或任何其他控制反转容器替换框架提供的 DI 容器(由 IServiceProvider
接口表示)。在我们的 DemoApp
中,我们将使用 ASP.NET Core 框架提供的默认依赖注入。
Startup
类的构造函数和 Configure
方法接受 IHostingEnvironment
和 ILoggerFactory
作为参数,以请求所需的相应服务。在以下代码中,我在 Diagnostics
包中添加了 WelcomePage
内置中间件。我只想为生产托管环境添加此中间件。我在 Configure
方法中将 IHostingEnvironment
作为参数包含进来,以获取有关环境的信息。
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// WelcomePage middleware added to pipeline for Production environment.
if (env.IsProduction())
{
app.UseWelcomePage();
}
要添加 environment
变量,请右键单击 DemoApp
,选择 **属性** > **调试**,然后单击 **添加** 以输入值,如下面的屏幕所示。这将创建或更新 DemoApp
项目下的 Properties 文件夹中的 launchSetting.json。
按 **F5** 开始调试,成功构建后将启动命令窗口。
应用程序启动后,请浏览到 https://:5000,以看到如下所示的欢迎页面
如果您想在不同的路径上看到上述页面,WelcomePageMiddleware
有一些重载版本。例如,UseWelcomePage(“/home”)
将仅在 https://:5000/home 路径上显示欢迎页面。
要使用 DI 容器可用的 GZip Compression 服务,我们需要在 Startup
类的 ConfigureServices
方法中添加中间件。
public void ConfigureServices(IServiceCollection services)
{
// add GZipCompression service
services.AddResponseCompression();
要使用最高压缩级别的 GZip 压缩,请在 Startup
类的 Configure
方法中将 ResponseCompression
中间件添加到 HTTP 管道。
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
// Response Compression Middleware
app.UseResponseCompression();
现在,如果您构建并浏览站点,并在浏览器的开发者工具中查看响应,您应该会看到响应内容被编码为 GZip。
我们将通过向 HTTP 请求管道添加文件服务功能来结束本教程。文件将从具有公共内容的文件夹的 Web 根目录提供。我希望 www 文件夹成为提供静态页面和单页应用程序 (SPA) 的根文件夹。我们可以使用 program.cs 中 WebHostBuilder
类的 UseWebRoot
扩展方法指定根文件夹。
var host = new WebHostBuilder()
.UseUrls("http://*:5000")
.UseKestrel()
.UseWebRoot(Directory.GetCurrentDirectory() + "/www")
.UseStartup<Startup>()
.Build();
通过在 Startup
类的 Configure
方法中添加以下静态文件中间件,我们可以为 www 文件夹及其子文件夹启用默认文件、提供静态文件和目录浏览。
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseDirectoryBrowser();
让我们在 launchSettings.json 中将环境更改为 Development,然后构建并运行 DemoApp
以验证添加到 HTTP 请求管道的文件服务功能。我在 Web 根文件夹的子文件夹中添加了 html 页面、单页应用程序和 Angularjs 客户端 API。您可以添加任何静态内容,如 html、图像、视频、css、文本、json 和 JavaScript。
结论
在本教程中,我演示了如何在新的 ASP.NET Core 框架上构建自定义的轻量级 HTTP 请求管道。我从头开始,然后将自定义中间件添加到请求管道。我在 Startup
类中使用依赖注入来配置中间件以提供自定义消息。我还向管道添加了 ASP.NET Core 的内置中间件。通常,我使用安装在计算机上的首选 IDE 进行开发,但在这里,我使用了命令行界面、Visual Studio Code 和 Visual Studio 2015 进行开发,并演示了在 Mac、Linux 和 Windows 上编译和运行 ASP.NET Core 应用程序。
参考文献
- https://docs.microsoft.com/en-us/dotnet/
- https://docs.microsoft.com/en-us/aspnet/
- https://github.com/aspnet/
历史
我理解这是一个冗长(tl;dr)的跨平台教程,关于一项新技术。我感谢您的建议或更正。我在这里所做的任何更改或改进都将在此处发布。
- 2016 年 12 月 27 日:添加了
DemoApp
的未编译源代码