中间件真面目

关于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. vuex vue-devtools 安装

    vue-devtools是一款基于chrome游览器的插件,用于调试vue应用,这可以极大地提高我们的调试效率.接下来我们就介绍一下vue-devtools的安装 chrome商店直接安装 谷歌访问助 ...

  2. Codeforces Round #622 (Div. 2) 1313 C1

    C1. Skyscrapers (easy version) time limit per test1 second memory limit per test512 megabytes inputs ...

  3. RobotFrameWork 自动化环境搭建(基于 python3.6)

    一.需要安装的工具目录 安装 python3.6.0 我这里采用的是 python3.6 (目前慢慢市场在推广 python3.0 版本了) 安装 wxPython(Python 非常有名的一个GUI ...

  4. Shell脚本(三)重定向

    先上一张图: 代码如下: #!/bin/bash echo "hello world" echo `ls +` 运行结果如下: PS: 1. 如果想同时将数据重定向到文件和stdo ...

  5. string操作大全

    1. string to int && int to string 2. 整数1转换成字符串"001" int sprintf ( char * str, cons ...

  6. Automatic Reference Counting

    NSObject简化版alloc: struct obj_layout { NSUInteger retained; }; + (id)alloc { int size = sizeof(struct ...

  7. E. Yet Another Task with Queens(分类思想)

    \(\color{Red}{描述}\) \(在n*n的棋盘上有m个K皇后,每个皇后可能被来自8个方向的其他皇后攻击\) \(每个皇后只可能被(0-8)只皇后攻击,分别求出被(0-8)只皇后攻击的皇后数 ...

  8. P1191 矩形

    ------------恢复内容开始------------ 题意 给出一个\(n*n\)的矩阵,矩阵中,有些格子被染成白色,有些格子被染成黑色,现要求矩阵中白色矩形的数量 分割线 Ⅰ.暴力出奇迹!! ...

  9. Coursera课程笔记----计算导论与C语言基础----Week 8

    C语言中的运算成分(Week 8) 赋值运算符 "="赋值运算符 给赋值号左边的变量赋予数值 在变量定义的同时可以为变量赋初值 要点一:两面类型不同 若=两边的类型不一致,赋值时要 ...

  10. 【HBase】HBase和Hue的整合

    目录 一.修改hue.ini配置文件 二.启动HBase的thrift server服务 三.启动Hue 四.页面访问 一.修改hue.ini配置文件 cd /export/servers/hue-3 ...