中间件真面目

关于ASP.NET Core中间件是啥,简单一句话描述就是:用来处理HTTP请求和响应的一段逻辑,并且可以决定是否把请求传递到管道中的下一个中间件!

上面只是概念上的一种文字描述,那问题来了,中间件在程序中到底是个啥

一切还是从IApplicationBuilder说起,没错,就是大家熟悉的Startup类里面那个Configure方法里面的那个IApplicationBuilder(有点绕,抓住重点就行)。

IApplicationBuilder,应用构建者,听这个名字就能感受它的核心地位,ASP.NET Core应用就是依赖它构建出来,看看它的定义:

public interface IApplicationBuilder
{
//...省略部分代码...
IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
RequestDelegate Build();
}

Use方法用来把中间件添加到应用管道中,此时我们已经看到中间件的真面目了,原来是一个委托,输入参数是RequestDelegate,返回也是RequestDelegate,其实RequestDelegate还是个委托,如下:

public delegate Task RequestDelegate(HttpContext context);

还记得中间件是干嘛的吗?是用来处理http请求和响应的,即对HttpContext的处理,这里我们可以看出来原来中间件的业务逻辑就是封装在RequestDelegate里面。

总结一下:

middleware就是Func<RequestDelegate, RequestDelegate>,输入的是下一个中间件的业务处理逻辑,返回的就是当前中间件的业务处理逻辑,并在其中决定要不要调用下个中间件!我们代码实现一个中间件看看(可能和我们平时用的不太一样,但它就是中间件最原始的形式!):

//Startup.Configure方法中
Func<RequestDelegate, RequestDelegate> middleware1 = next => async (context) =>
{
//处理http请求 Console.WriteLine("do something before invoke next middleware in middleware1");
//调用下一个中间件逻辑,当然根据业务实际情况,也可以不调用,那此时中间件管道调用到此就结束来了! await next.Invoke(context);
Console.WriteLine("do something after invoke next middleware in middleware1");
};
//添加到应用中 app.Use(middleware1);

跑一下瞅瞅,成功执行中间件!

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<RequestDelegate, RequestDelegate> middleware);
RequestDelegate Build();
}

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;
}
  • Build方法里面定义了一个 RequestDelegate ,作为最后一个处理逻辑,例如返回404。

  • _components存储着添加的所有中间件

  • 中间件管道调度顺序,就是按照中间添加的顺序调用,所以中间件的顺序很重要,很重要,很重要!

  • 遍历_components,传入next RequestDelegate,获取当前RequestDelegate,完成管道构建!

中间件使用

在此之前,还是提醒下,中间件最原始的使用姿势就是

IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);

下面使用的方式,都是对此方式的扩展!

Lamda方式

大多数教程里面都提到的方式,直接上代码:

//扩展方法
//IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware)
app.Use(async (context, next) =>
{
Console.WriteLine("in m1");
await next.Invoke();
Console.WriteLine("out m1");
});

扩展方法简化了中间件的使用,这个里面就只负责写核心逻辑,然后扩展方法中把它包装成Func<RequestDelegate, RequestDelegate>类型进行添加,不像原始写的那样复杂,我们看下这个扩展方法实现,哈,原来就是一个简单封装!我们只要专注在middleware里面写核心业务逻辑即可。

public static IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware)
{
return app.Use(next =>
{
return context =>
{
Func<Task> simpleNext = () => next(context);
return middleware(context, simpleNext);
};
});
}

如果我们定义中间件作为终端中间件(管道流转此中间件就结束了,不再调用后面的中间件)使用时,上面只要不调用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, TestService>();
}
//中间件类,注入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<CustomeMiddleware1>();

运行一下,跑出来的结果如下,完美!

等一下,有没有发现上面有啥问题???

明明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<CustomeMiddleware>();

运行一下,结果报错了... ,提示CustomeMiddleware没有注册!

InvalidOperationException: No service for type 'WebApplication3.CustomeMiddleware' has been registered.

通过报错信息,我们已经知道,如果实现了IMiddleware接口的中间件,他们并不是在应用启动时就实例化好的,而是每次都是从IOC容器中获取的,其中就是IMiddlewareFactory

来解析出对应类型的中间件的(内部就是调用IServiceProvider),了解到此,我们就知道,此类中间件此时是需要以service的方式注册到IOC容器里面的,这样中间件就可以根据注册时候指定的生命周期方式来实例化,从而解决了我们上一节提出的疑问了!好了,我们注册下中间件服务

public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<CustomeMiddleware>();
services.AddTransient<ITestService, TestService>();
}

再次多次刷新请求,返回都是下面的内容

TestService.Test is called 1 times
CustomeMiddleware invoked 1 times

结语

中间件存在这么多的使用方式,每一个存在都是为了解决实际需求的,当我们了解这些背景知识后,在后面自己使用时,就能更加的灵活!

换个角度学习ASP.NET Core中间件的更多相关文章

  1. ASP.NETCore学习记录(二) —— ASP.NET Core 中间件

    ASP.NET Core 中间件 目录: 什么是中间件 ? IApplicationBuilder 使用 IApplicationBuilder 创建中间件 Run.Map 与 Use 方法 实战中间 ...

  2. 学习ASP.NET Core,你必须了解无处不在的“依赖注入”

    ASP.NET Core的核心是通过一个Server和若干注册的Middleware构成的管道,不论是管道自身的构建,还是Server和Middleware自身的实现,以及构建在这个管道的应用,都需要 ...

  3. ASP.NET Core中间件(Middleware)实现WCF SOAP服务端解析

    ASP.NET Core中间件(Middleware)进阶学习实现SOAP 解析. 本篇将介绍实现ASP.NET Core SOAP服务端解析,而不是ASP.NET Core整个WCF host. 因 ...

  4. 如何一秒钟从头构建一个 ASP.NET Core 中间件

    前言 其实地上本没有路,走的人多了,也便成了路. -- 鲁迅 就像上面鲁迅说的那样,其实在我们开发中间件的过程中,微软并没有制定一些策略或者文档来约束你如何编写一个中间件程序, 但是其中却存在者一些最 ...

  5. 从明面上学习ASP.NET Core

    一.前言     这篇文章就是从能看到地方去学习Core,没有很深奥,也没有很难懂,现在我们开始吧. 二.构建项目,引发思考     创建项目的步骤真的很简单,你要是不会,我真也没法了,我这是创建的M ...

  6. 学习ASP.NET Core,你必须了解无处不在的“依赖注入”(转载)

    ASP.NET Core的核心是通过一个Server和若干注册的Middleware构成的管道,不论是管道自身的构建,还是Server和Middleware自身的实现,以及构建在这个管道的应用,都需要 ...

  7. 从零开始学习 asp.net core 2.1 web api 后端api基础框架(二)-创建项目

    原文:从零开始学习 asp.net core 2.1 web api 后端api基础框架(二)-创建项目 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.ne ...

  8. ASP.NET Core 中间件的使用(二):依赖注入的使用

    写在前面 上一篇大家已经粗略接触了解到.NET Core中间件的使用:ASP .Net Core 中间件的使用(一):搭建静态文件服务器/访问指定文件, .NET Core框架中很多核心对象都是通过依 ...

  9. 如何将IHttpHandler和IHttpModule迁移到ASP.NET Core中间件

    ASP.NET Core是一个跨平台.开源的框架,用于在Windows.Mac和Linux操作系统(OS)上开发web应用程序.你可以使用以下任何IDE开发ASP.NET Core 应用程序: Vis ...

随机推荐

  1. Git 常见问题汇总

    git reabse 合并多个commit git rebase -i HEAD~{NUM} git rebase -i start end 会进入一个指定区间的commit列表 根据提示进行文本编辑 ...

  2. 深度解析国内首个云原生数据库POLARDB的“王者荣耀”

    随着移动互联网.电子商务的高速发展,被使用最多的企业级开源数据系统MySQL面临着巨大挑战--为迎接"双11"的高并发要提前做好分库分表;用户不断激增要将读写分离才能应对每天上亿次 ...

  3. VMware虚拟机中centos6.5网络配置(桥接方式)与宿主机之间通信

    1.修改网络适配器 2.选择桥接所用的网卡 3.设置网络 3.1在系统终端中输入 setup ,进行图形网络配置(此命令只有redhat系列才有作用) 上下左右键选择,enter键确定 将光标移动到U ...

  4. C语言入门经典题目及其答案

    写在开始: 我叫风骨散人,名字的意思是我多想可以不低头的自由生活,可现实却不是这样.家境贫寒,总得向这个世界低头,所以我一直在奋斗,想改变我的命运给亲人好的生活,希望同样被生活绑架的你可以通过自己的努 ...

  5. 网课应该这么刷(油猴Tampermonkey脚本自动刷课)

    懒人福利 首先有些人不想学怎么用脚本,满足你们,压缩包解压之后直接登录即可.戳我下载 脚本已经集成好了,登录即可刷课.章节测试还会自动答题呦,正确率高达97%呦. 油猴及脚本安装 油猴的脚本不知可以刷 ...

  6. DP背包(一)

    01背包 for(int i=0;i<n;i++) //遍历每一件物品 for(int j=v;j>=wei[i];j--)//遍历背包容量,表示在上一层的基础上,容量为J时,第i件物品装 ...

  7. CentOS7 安装boost

    (1)到这个网址www.boost.org下载相应的代码包,我下载的是目前最新的版本boost_1_59_0.tar.bz2 (2)进入目录执行解压操作:tar -jxvf boost_1_59_0. ...

  8. Java中常用七个阻塞队列的总结

    Java队列总结 通过前面文章的学习,我们对Java中常用队列做了介绍.本文,咱们来对队列做个总结吧. 首先,我们介绍了现实生活中的实际场景(排队买票等),来告诉我们为什么需要使用队列. 队列是一种先 ...

  9. MySQL命令3

    连接 mysql -u username -p 查看进程 SHOW processlist

  10. Redis服务器和客户端的通信

    Redis客户端使用RESP(Redis序列化协议)与Redis服务器进行通信,RESP在位于TCP之上,而网络模型上客户端和服务器是保持的双工的连接.如图1 而一个简单的请求/响应的串行通信模型如下 ...