换个角度学习ASP.NET Core中间件
2021-02-18 01:20
标签:index time number ams 包装 简化 重点 dex without
关于ASP.NET Core中间件是啥,简单一句话描述就是:用来处理HTTP请求和响应的一段逻辑,并且可以决定是否把请求传递到 上面只是概念上的一种文字描述,那问题来了,中间件在程序中到底是个啥? 一切还是从 还记得中间件是干嘛的吗?是用来处理http请求和响应的,即对 总结一下: 跑一下瞅瞅,成功执行中间件! 通过上面我们有没有注意到,添加中间时,他们都是一个一个独立的被添加进去,而中间件管道就是负责把中间件串联起来,实现下面的一个中间件调用流转流程: 如何实现呢?这个就是 看下框架默认实现: Build方法里面定义了一个 RequestDelegate ,作为最后一个处理逻辑,例如返回404。 _components存储着添加的所有中间件 中间件管道调度顺序,就是按照中间添加的顺序调用,所以中间件的顺序很重要,很重要,很重要! 遍历_components,传入next RequestDelegate,获取当前RequestDelegate,完成管道构建! 在此之前,还是提醒下,中间件最原始的使用姿势就是 下面使用的方式,都是对此方式的扩展! 大多数教程里面都提到的方式,直接上代码: 扩展方法简化了中间件的使用,这个里面就只负责写核心逻辑,然后扩展方法中把它包装成 如果我们定义中间件作为 当然我们还有另外一个选择,自己使用扩展 到此,我们有没有发现上面的方式有些弊端,只能处理下简单逻辑,如果要依赖第三方服务,那可怎么办? 使用中间件类,我们只要按照 使用类后,我们就可以注入我们需要的第三方服务,然后完成更复杂的业务逻辑,上代码 运行一下,跑出来的结果如下,完美! 等一下,有没有发现上面有啥问题???? 明明 这个时候我们应该发现,我们上面的所有方式添加的中间件的生命周期其实和应用程序是一致的,也就是说是只在程序启动的时候实例化一次!所以这里第三方的服务,然后以 如果我们就是要在中间件里面是有 运行一下,结果报错了... ,提示CustomeMiddleware没有注册! 通过报错信息,我们已经知道,如果实现了 来解析出对应类型的中间件的(内部就是调用IServiceProvider),了解到此,我们就知道,此类中间件此时是需要以service的方式注册到 再次多次刷新请求,返回都是下面的内容 中间件存在这么多的使用方式,每一个存在都是为了解决实际需求的,当我们了解这些背景知识后,在后面自己使用时,就能更加的灵活! 换个角度学习ASP.NET Core中间件 标签:index time number ams 包装 简化 重点 dex without 原文地址:https://www.cnblogs.com/lonelyxmas/p/12945217.html中间件真面目
管道
中的下一个中间件!IApplicationBuilder
说起,没错,就是大家熟悉的Startup
类里面那个Configure
方法里面的那个IApplicationBuilder
(有点绕??,抓住重点就行)。IApplicationBuilder
,应用构建者,听这个名字就能感受它的核心地位,ASP.NET Core应用就是依赖它构建出来,看看它的定义:public interface IApplicationBuilder
{
//...省略部分代码...
IApplicationBuilder Use(Func
Use
方法用来把中间件添加到应用管道
中,此时我们已经看到中间件的真面目了,原来是一个委托,输入参数是RequestDelegate
,返回也是RequestDelegate
,其实RequestDelegate
还是个委托,如下:public delegate Task RequestDelegate(HttpContext context);
HttpContext
的处理,这里我们可以看出来原来中间件的业务逻辑就是封装在RequestDelegate
里面。middleware
就是Func
,输入的是下一个中间件的业务处理逻辑,返回的就是当前中间件的业务处理逻辑,并在其中决定要不要调用下个中间件!我们代码实现一个中间件看看(可能和我们平时用的不太一样,但它就是中间件最原始的形式!)://Startup.Configure方法中
Func
IIS Express is running.
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: E:\vs2019Project\WebApplication3\WebApplication3
do something before invoke next middleware in middleware1
do something after invoke next middleware in middleware1
中间件管道
IApplicationBuilder
中的Build
的职责了,再次看下定义:public interface IApplicationBuilder
{
//...省略部分代码...
IApplicationBuilder Use(Func
Build
方法一顿操作猛如虎,主要干一件事把中间件串联起来,最后返回了一个 RequestDelegate
,而这个就是我们添加的第一个中间件返回的RequestDelegate
,//ApplicationBuilder.cs
public RequestDelegate Build()
{
RequestDelegate app = context =>
{
// If we reach the end of the pipeline, but we have an endpoint, then something unexpected has happened.
// This could happen if user code sets an endpoint, but they forgot to add the UseEndpoint middleware.
var endpoint = context.GetEndpoint();
var endpointRequestDelegate = endpoint?.RequestDelegate;
if (endpointRequestDelegate != null)
{
var message =
$"The request reached the end of the pipeline without executing the endpoint: ‘{endpoint.DisplayName}‘. " +
$"Please register the EndpointMiddleware using ‘{nameof(IApplicationBuilder)}.UseEndpoints(...)‘ if using " +
$"routing.";
throw new InvalidOperationException(message);
}
context.Response.StatusCode = 404;
return Task.CompletedTask;
};
foreach (var component in _components.Reverse())
{
app = component(app);
}
return app;
}
中间件使用
IApplicationBuilder Use(Func
Lamda方式
//扩展方法
//IApplicationBuilder Use(this IApplicationBuilder app, Func
Func
类型进行添加,不像原始写的那样复杂,我们看下这个扩展方法实现,哈,原来就是一个简单封装!我们只要专注在middleware
里面写核心业务逻辑即可。public static IApplicationBuilder Use(this IApplicationBuilder app, Func
终端中间件
(管道流转此中间件就结束了,不再调用后面的中间件)使用时,上面只要不调用next
即可。Run
方法,传入的参数就是RequestDelegate
,还是上代码://扩展方法
//public static void Run(this IApplicationBuilder app, RequestDelegate handler);
app.Run(async (context) =>
{
Console.WriteLine("in m3");
await context.Response.WriteAsync("test22");
Console.WriteLine("out m3");
});
定义中间件类方式
约定
的方式,即类中包含InvokeAsync
方法,就可以了。//定义第三方服务
public interface ITestService
{
Task Test(HttpContext context);
}
public class TestService : ITestService
{
private int _times = 0;
public Task Test(HttpContext context)
{
return context.Response.WriteAsync($"{nameof(TestService)}.{nameof(TestService.Test)} is called {++_times} times\n");
}
}
//添加到IOC容器
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient
//中间件类,注入ITestService
public class CustomeMiddleware1
{
private int _cnt;
private RequestDelegate _next;
private ITestService _testService;
public CustomeMiddleware1(RequestDelegate next, ITestService testService)
{
_next = next;
_cnt = 0;
_testService = testService;
}
public async Task InvokeAsync(HttpContext context)
{
await _testService?.Test(context);
await context.Response.WriteAsync($"{nameof(CustomeMiddleware1)} invoked {++_cnt} times");
}
}
//添加中间件,还是一个扩展方法,预知详情,请看源码
app.UseMiddleware
ITestService
是以Transient
注册到容器里面,应该每次使用都是新实例化的,那不应该被显示被调用 15 次啊!!!Transient
方式注册到容器,但在中间件里面变现出来就是一个单例
效果,这就为什么我们不建议在中间件里面注入DbContext
了,因为DbContext
我们一般是以Scoped
来用的,一次http请求结束,我们就要释放它!ITestService
,而且还是Transient
的效果,怎么办?实现IMiddleware接口
//接口定义
public interface IMiddleware
{
ask InvokeAsync(HttpContext context, RequestDelegate next);
}
//实现接口
public class CustomeMiddleware : IMiddleware
{
private int _cnt;
private ITestService _testService;
public CustomeMiddleware(ITestService testService)
{
_cnt = 0;
_testService = testService;
}
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
await _testService?.Test(context);
await context.Response.WriteAsync($"{nameof(CustomeMiddleware)} invoked {++_cnt} times");
}
}
//添加中间件
app.UseMiddleware
InvalidOperationException: No service for type ‘WebApplication3.CustomeMiddleware‘ has been registered.
IMiddleware
接口的中间件,他们并不是在应用启动时就实例化好的,而是每次都是从IOC容器
中获取的,其中就是IMiddlewareFactoryIOC容器
里面的,这样中间件就可以根据注册时候指定的生命周期方式来实例化,从而解决了我们上一节提出的疑问了!好了,我们注册下中间件服务public void ConfigureServices(IServiceCollection services)
{
services.AddTransient
TestService.Test is called 1 times
CustomeMiddleware invoked 1 times
结语
文章标题:换个角度学习ASP.NET Core中间件
文章链接:http://soscw.com/index.php/essay/56832.html