前几天,公众号后台有朋友在问Core的中间件,因此专门抽时间整理了这样一篇文章。html
中间件(Middleware)最初是一个机械上的概念,说的是两个不一样的运动结构中间的链接件。后来这个概念延伸到软件行业,你们把应用操做系统和电脑硬件之间过渡的软件或系统称之为中间件,比方驱动程序,就是一个典型的中间件。再后来,这个概念就泛开了,任何用来链接两个不一样系统的东西,都被叫作中间件。web
因此,中间件只是一个名词,不用太在乎,实际代码跟他这个词,也没太大关系。json
中间件技术,早在.Net framework时期就有,只不过,那时候它不是Microsoft官方的东西,是一个叫OWIN的三方框架下的实现。到了.Net core,Microsoft才把中间件完整加到框架里来。c#
感受上,应该是Core参考了OWIN的中间件技术(猜的,未求证)。在实现方式上,两个框架下的中间件没什么区别。api
下面,咱们用一个实际的例子,来理清这个概念。bash
为了防止不提供原网址的转载,特在这里加上原文连接:http://www.javashuo.com/article/p-wlqxqubg-dh.html微信
这个Demo的开发环境是:Mac + VS Code + Dotnet Core 3.1.2。app
$ dotnet --info
.NET Core SDK (reflecting any global.json):
Version: 3.1.201
Commit: b1768b4ae7
Runtime Environment:
OS Name: Mac OS X
OS Version: 10.15
OS Platform: Darwin
RID: osx.10.15-x64
Base Path: /usr/local/share/dotnet/sdk/3.1.201/
Host (useful for support):
Version: 3.1.3
Commit: 4a9f85e9f8
.NET Core SDKs installed:
3.1.201 [/usr/local/share/dotnet/sdk]
.NET Core runtimes installed:
Microsoft.AspNetCore.App 3.1.3 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 3.1.3 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
首先,在这个环境下创建工程:框架
% dotnet new sln -o demo
The template "Solution File" was created successfully.
% cd demo
% dotnet new webapi -o demo
The template "ASP.NET Core Web API" was created successfully.
Processing post-creation actions...
Running 'dotnet restore' on demo/demo.csproj...
Restore completed in 179.13 ms for demo/demo.csproj.
Restore succeeded.
% dotnet sln add demo/demo.csproj
基础工程搭建完成。async
咱们先看下Demo项目的Startup.cs
文件:
namespace demo
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
/* This method gets called by the runtime. Use this method to add services to the container. */
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
/* This method gets called by the runtime. Use this method to configure the HTTP request pipeline. */
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
这是Startup
默认生成后的样子(注意,不一样的操做系统下生成的代码会略有不一样,但本质上没区别)。
其中,Configure
是中间件的运行定义,ConfigureServices
是中间件的参数设置注入。
咱们在Configure
方法里,加入一个简单的中间件:
app.UseAuthorization();
/* 下面是加入的代码 */
app.Use(async (context, next) =>
{
/* your code */
await next.Invoke();
/* your code */
});
/********************/
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
在这个代码中,app.Use
是引入中间件的方式,而真正的中间件,是async (context, next)
,这是一个delegate
方法。
中间件方法的两个参数,context
是上下文HttpContext
,next
指向下一个中间件。
其中,next
参数很重要。中间件采用管道的形式执行。多个中间件,经过next
进行调用。
在前一节,咱们看到了中间件的标准形式。
有时候,咱们但愿中间件执行完成后就退出执行,而不进入下一个中间件。这时候,咱们能够把await next.Invoke()
从代码中去掉。变成下面的形式:
app.Use(async (context, next) =>
{
/* your code */
});
对于这种形式,Microsoft给出了另外一个方式的写法:
app.Run(async context =>
{
/* your code */
});
这两种形式,效果彻底同样。
这种形式,咱们称之为短路,就是说在这个中间件执行后,程序即返回数据给客户端,而不执行下面的中间件。
有时候,咱们须要把一个中间件映射到一个Endpoint
,用以对外提供简单的API处理。这种时间,咱们须要用到映射:
app.Map("/apiname", apiname => {
app.Use(async (context, next) =>
{
/* your code */
await next.Invoke();
});
});
此外,映射支持嵌套:
app.Map("/router", router => {
router.Map("/api1name", api1Name => {
app.Use(async (context, next) =>
{
/* your code */
await next.Invoke();
});
});
router.Map("/api2name", api2Name => {
app.Use(async (context, next) =>
{
/* your code */
await next.Invoke();
});
});
})
对于这两个嵌套的映射,咱们访问的Endpoint
分别是/router/api1name
和/router/api2name
。
以上部分,就是中间件的基本内容。
可是,这儿有个问题:为何咱们从各处文章里看到的中间件,好像都不是这么写的?
嗯,答案其实很简单,咱们看到的方式,也都是中间件。只不过,那些代码,是这个中间件的最基本样式的变形。
下面,咱们就来讲说变形。
大多数状况下,咱们但愿中间件能独立成一个类,方便控制,也方便程序编写。
这时候,咱们能够这样作:先写一个类:
public class TestMiddleware
{
private readonly RequestDelegate _next;
public TestMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
/* your code */
await _next.Invoke(context);
}
}
这个类里context
和next
和上面简单形式下的两个参数,类型和意义是彻底同样的。
下面,咱们把这个类引入到Configure
中:
app.UseMiddleware<TestMiddleware>();
注意,引入Middleware类,须要用app.UseMiddleware
而不是app.Use
。
app.Use
引入的是方法,而app.UseMiddleware
引入的是类。就这点区别。
若是再想高大上一点,能够作个Extensions:
public static class TestMiddlewareExtensions
{
public static IApplicationBuilder UseTestMiddleware(this IApplicationBuilder app)
{
return app.UseMiddleware<TestMiddleware>();
}
}
而后,在Configure
中,就能够换成:
app.UseTestMiddleware();
看着高大上了有没有?
有时候,咱们须要给在中间件初始化时,给它传递一些参数。
看类:
public class TestMiddleware
{
private readonly RequestDelegate _next;
private static object _parameter
public TestMiddleware(RequestDelegate next, object parameter)
{
_next = next;
_parameter = parameter;
}
public async Task Invoke(HttpContext context)
{
/* your code */
await _next.Invoke(context);
}
}
那相应的,咱们在Configure
中引入时,须要写成:
app.UseMiddleware<TestMiddleware>(new object());
同理,若是咱们用Extensions时:
public static class TestMiddlewareExtensions
{
public static IApplicationBuilder UseTestMiddleware(this IApplicationBuilder app, object parameter)
{
return app.UseMiddleware<TestMiddleware>(parameter);
}
}
同时,引入变为:
app.UseTestMiddleware(new object());
跟前一节同样,咱们须要引入参数。这一节,咱们用另外一种更优雅的方式:依赖注入参数。
先建立一个interface:
public interface IParaInterface
{
void someFunction();
}
再根据interface建立一个实体类:
public class ParaClass : IParaInterface
{
public void someFunction()
{
}
}
参数类有了。下面创建中间件:
public class TestMiddleware
{
private readonly RequestDelegate _next;
private static IParaInterface _parameter
public TestMiddleware(RequestDelegate next, IParaInterface parameter)
{
_next = next;
_parameter = parameter;
}
public async Task Invoke(HttpContext context)
{
/* your code */
/* Example: _parameter.someFunction(); */
await _next.Invoke(context);
}
}
由于咱们要采用注入而不是传递参数,因此Extensions不须要关心参数:
public static class TestMiddlewareExtensions
{
public static IApplicationBuilder UseTestMiddleware(this IApplicationBuilder app)
{
return app.UseMiddleware<TestMiddleware>();
}
}
最后一步,咱们在Startup
的ConfigureServices
中加入注入代码:
services.AddTransient<IParaInterface, ParaClass>();
完成 !
这个方式是Microsoft推荐的方式。
我在前文Dotnet core使用JWT认证受权最佳实践中,在介绍JWT配置时,实际使用的也是这种方式。
app.UseAuthentication();
这是Microsoft已经写好的认证中间件,咱们只简单作了引用。
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(option =>
{
option.RequireHttpsMetadata = false;
option.SaveToken = true;
var token = Configuration.GetSection("tokenParameter").Get<tokenParameter>();
option.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
ValidIssuer = token.Issuer,
ValidateIssuer = true,
ValidateAudience = false,
ClockSkew = TimeSpan.Zero,
};
});
这部分代码,是咱们注入的参数设置。其中,几个方法又是Microsoft库提供的Builder。
Builder是注入参数的另外一种变形。我会在关于注入和依赖注入中详细说明。
中间件的引入次序,从代码上来讲没有那么严格。就是说,某些类型的中间件交换次序不会有太大问题。
通常来讲,使用中间件的时候,能够考虑如下规则:
以这两个规则来决定中间件的引入次序,就足够了。
(全文完)
![]() |
微信公众号:老王Plus 扫描二维码,关注我的公众号,能够第一时间获得最新的我的文章和内容推送 本文版权归做者全部,转载请保留此声明和原文连接 |