如何一秒钟从头构建一个 ASP.NET Core 中间件
就像上面鲁迅说的那样,其实在我们开发中间件的过程中,微软并没有制定一些策略或者文档来约束你如何编写一个中间件程序, 但是其中却存在者一些最佳实践的方法,大多数人来使用这种方法来使应用程序变得更加容易理解并且易于维护,这就叫“路”,在2017年,这叫套路。
在掌握了这些套路之后,能够帮助你迅速的搭建一个中间件的基本框架,并且易于扩展和维护,下面我们就来看看怎么样从头开始开发一个中间件吧。
如果你对 ASP.NET Core HTTP 管道还不太清楚的话,下面这篇文章将有助于你对其进行一个系统的了解:
http://www.cnblogs.com/savorboard/p/aspnetcore-http-pipeline.html
Getting Started
说明: 这只是通常情况下,具体的情况还请使用具体的套路。
Setup 1 创建扩展类
如果你的中间件需要一个向 ASP.NET Core 的 DI 容器中添加默认的一些服务的话,那么你就编写一个需要扩展类,用来在 Startup.cs 中的 ConfigureServices 中注册服务。
举例,Microsoft.AspNetCore.ResponseCompression 这是一个用来压缩请求内容的一个中间件,那么它就需要一个服务用来处理压缩相关的东西,所以它扩展了 IServiceCollection 并且添加了自己的 Services。
整个中间件的核心代码并非在这里,这里只是一个开始,那么有同学可能会问了,什么情况下我们需要提前向一个DI里面注入我们中间件需要的服务呢? 答案是,如果你不知道或者不确定你需要什么样的服务的时候,跳过此步骤,进入下一步,再等你需要的时候再回头来补上就是。
那么,我们先看一下编写一个扩展Service的静态类应该怎么做?
首先,新建一个以 xxxServicesExtensions
文件名结尾的静态类,用来编写注入DI的扩展方法。
类建立完成之后,需要向里面添加内容了。通常情况下,中间件中 Service 的扩展方法都是以 Addxxx(this IServiceCollection services)
开头来命名。在这里有一个需要注意的地方就是它的命名空间,通常情况下我们使用 using Microsoft.AspNetCore.Builder
这个命名空间。
然后,方法里面就是需要注册的服务了。假设我们需要向里面注册一个 IResponseCompressionProvider
和 它的实现类 ResponseCompressionProvider
,那么最终的扩展方法可能看起来是这样的。
using System;
using Microsoft.AspNetCore.ResponseCompression;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Microsoft.AspNetCore.Builder
{
public static IServiceCollection AddResponseCompression(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.TryAddSingleton<IResponseCompressionProvider, ResponseCompressionProvider>();
return services;
}
}
Setup 2 创建配置类
有的时候,用户在使用我们编写的中间件的时候,我们需要向提供者提供一些配置项,经典影片推荐这些配置项在中间件执行之前用来传递一些必要参数信息或者是一些设置信息。举个我们熟悉例子,我们在使用 MVC 中间件的时候,可能会看到以下写法:
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
var userDefinedFilter = new xxxFilter();
services.AddMvc(x => x.Filters.Add(userDefinedFilter));
}
可以看到,用户可将一些自定义的 Filter 传入到中间件的,然后中间件在运行的时候,我们传入的 Filter 就生效了。
注意,中间件使用的配置项有两种添加方法,一种是添加到
AddMiddleware(Action<xxxOptions> option)
另外一种是UseMiddleware<>(Action<xxxOptions> option)
,那么这两种有什么区别呢?
那么,前者Add中的配置项一般情况下是中间执行之前就需要的一些信息,也就是说中间件的启动就依赖于这些配置项,他放置于容器配置(Add DI Service)的时候添加进去更加方便或者合适的时候使用它,另外一种(后者)是容器已经构建完毕,不需要依赖于容器提供的配置项可以使用此种方式。
同样的道理,当你自己为你的用户编写一个中间件的时候,当你也需要用户可以自定义一些配置或者需要传入一些参数的时候,留学出国条件你也可以这么做。那到底怎么样做呢? 我们一起来看看。
首先,我们需要一个 xxxOptions
结尾的配置类,用来配置我们中间件需要的一些配置项。我们还是以上面的压缩中间件举例。
public class GzipCompressionProviderOptions : IOptions<GzipCompressionProviderOptions>
{
public CompressionLevel Level { get; set; } = CompressionLevel.Fastest;
GzipCompressionProviderOptions IOptions<GzipCompressionProviderOptions>.Value => this;
}
它其中配置了一个压缩的等级 CompressionLevel
,这是一个枚举字段。 然后我们可以看到,这个类它继承了 IOptions<out T>
接口,这是一个知识点,什么意思呢? IOptions<out TOptions>
是 ASP.NET Core 新的一个配置体系里面的一个接口,当你实现这个接口之后,ASP.NET Core DI 容器提供了了一个 services.Configure<xxxOptions>
这样的方法来让你把配置项注入到容器中,当然你也可以将配置项和 appsetting.json 中的配置关联起来,以便于配置一些在运行期可能需要变动信息。更多关于 IOptions<T>
的信息可以看 这里的翻译。
这个 xxxOptions
类通常情况下会提供一些默认值,也就是说当用户不提供这些参数的时候,你需要有一个合理的机制或者默认值来正常运行你的中间件。
假如你的配置项有很多,也就是说还有进一步比较细化的配置,那么你可以做一个封装,就像MVC的Options类一样,这样能够给你的中间件提供更加合理的维护和扩展。
Setup 3 核心中间件
接下来,就是我们的核心代码类了,通常情况下会有一个 xxxMiddleware
结尾的类用来处理 HTTP 管道请求中的一些业务,这个类的构造函数中已经可以使用在Setup1或者Setup2中向DI容器中注册的服务了。
按照约定,Middleware 类中需要有一个 Invoke 方法,用来处理中间件的核心业务,它的签名如下:
public Task Invoke(HttpContext httpContext);
注意,这是一个约定方法,并没有接口来约束它。在 Invoke
方法中,是中间件实现的核心代码。 示例如下:
public class xxxMiddleware
{
private readonly RequestDelegate _next;
public xxxMiddleware(RequestDelegate next)
{
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
_next = next;
}
public async Task Invoke(HttpContext context)
{
// ......
await _next(context);
return;
}
}
在 xxxMiddleware
这个里面有一个构造函数参数 RequestDelegate
,它是一个委托,代表的需要执行的下一个中间件,通常情况下我们会把它放到我们业务代码的末尾。
Setup 4 中间件扩展注册
中间件有三种注册方法(Run,Map,Use),我们暂不考虑Run和Map,因为他们只适用于很小和少的一些情况
完成了以上工作后,接下来,我们需要把中间件注册到我们的 ASP.NET Core 的执行,这个时候我们需要一个 xxxBuilderExtensions
类,它也是一个静态类,注意它的命名空间通常为Microsoft.AspNetCore.Builder
,因为这个用户在使用我们的中间件的时候就不必再添加额外的命令空间,依靠 Visual Studio 的智能提示就可以很快速的搜索到。我们来看一下示例:
using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Microsoft.AspNetCore.Builder
{
public static class xxxBuilderExtensions
{
public static IApplicationBuilder UseResponseCompression(this IApplicationBuilder builder)
{
if (builder == null)
{
throw new ArgumentNullException(nameof(builder));
}
return builder.UseMiddleware<xxxMiddleware>();
}
}
}
Yeoman 一秒钟
有同学可能会说了,这些套路既然是这样的,那么有没有什么代码生成工具来帮我做这些事情呢?答案是肯定的。
博主已经帮你们把工具做好了,它使用的是当今最流行的脚手架工具 npm 中的 Yeoman 。使用它可以帮助你迅速的搭建一个中间件解决方案代码模板,让你专注于业务开发。
我已经把这个模板上传于 Yeoman 的仓库中,你只需要按照如下命令就可以帮你自动生成一套 ASP.NET Core 中间件解决方案代码模板了,当然单元测试也包含其中。
npm 工具的安装相信你自己可以的。下面是安装 Yeoman 工具和博主的模板工具。
// 安装 Yeoman 脚手架工具 -g 命令为全局安装
npm install -g yo
// 安装博主的 Yeoman(ASP.NET Core Middleware)模板
npm install -g generator-aspnetcore-middleware
然后选择你需要生成解决方案的文件夹,使用如下命令生成。
yo aspnetcore-middleware
注意:生成的过程中需要输入你中间件的名称。按要求输入即可。
总结
本篇文章主要讲述了从头创建一个 ASP.NET Core 的流程,这适用于大多数场合,但是并不代表所有的场合,在实际开发的过程中还需要具体的考虑一下。接着博主提供了一个yo自动化脚手架模板用来快速创建一个中间件解决方案。
如果你觉得这篇文章对你有帮助的话,谢谢你的【推荐】。
如果你对 .NET Core 感兴趣可以关注我,我会定期在博客分享关于 .NET Core 的学习心得。