首页 > 嗟来之食 > 【ASP.NET Core】给中间件传参数的方法
2018
06-06

【ASP.NET Core】给中间件传参数的方法

最近博客更新频率慢了些,原因有三:
其一,最近老周每星期六都录 ASP.NET Core 的直播,有些内容在视频里讲过,就不太想在博客里面重复。有兴趣的话可以去老周的微博看,或者去一直播,直播帐号与微博帐号是绑定的;
其二,最近老周是有些忙,但不是忙写代码的事情。而是忙着“寻宝藏”。
其三,每个星期至少也要抽出一天的时间,跟妹子出去浪。准确地说,应该叫对象(Object),是通过构造函数认识的,已经顺利运行有五个月了。目前状态良好,内存占用小,不烧 CPU。性能好,不吃硬件。
 
好了,屁话不多说了。今天说说如何向中间件传参数的事。
首先,用宇宙中最通俗的语言介绍一下啥是中间件。浏览器或客户端向服务器发出请求后,HTTP 请求会进入通信管道,然后由一个个中间件来进行处理,A 处理完了,交给 B;B 处理完了再交给 C……所有中间件就串成一条链,直到最后一个中间件(HTTP 404)。处理完后就把结果返回给调用者。
所以说,中间件就是一个处理 HTTP 请求的“零件”,比如,你可以定义一个中间件,对请求中的某些数据进行解密或者验证。或者,你可以定义一个中间件来添加自己定义的 HTTP 头。
中间件在代码中由一个 RequestDelegate 委托来表示,该委托声明如下。

delegate System.Threading.Tasks.Task RequestDelegate(Microsoft.AspNetCore.Http.HttpContext context)

HttpContext 可以保持整个 HTTP 请求上下文中的数据与状态,并且在调用每一个中间件时,会将自身传递过去。返回类型为 Task,表示支持异步等待。
对于代码简单的中间件,可以直接 Use 扩展方法,比如这样。

app.Use(async (context, next) =>
{
context.Response.Headers.Add("key", "no key");
await next();
});

next 是指处理链条上的下一个中间件,如果没特殊事情,在当前代码处理完后应该调用下一个中间件。当然,如果没有必要调用下一个中间件,那可以不调用 next,这样,整个 HTTP 通信管道就中止了。
但是,如果中间件逻辑多,代码多,而且又要接收参数的话,那就应该用一个类来封装。封装的时候一定要注意相关的约定。
中间件必须包含一个 Invoke 或 InvokeAsync 方法,方法的参数为 HttpContext,返回类型为 Task。
为什么要这个约定呢?你看看刚刚那个 RequestDelegate 委托。委托类型的实例是不是有个 Invoke 方法?这就对了,大概为了方便记忆,所以代码约定也使用了 Invoke 这名字。在运行的时候,框架会在中间件类中寻找名字为 Invoke 或 InvokeAsync 的方法。所以,要想自定义的中间件类起作用,你应该遵守这个约定。
我们今天讨论的重点是向中间件传参数,要能传参的话,就必须写一个中间件类。先说说,怎么传参。方法有二。
一、IApplicationBuilder 的 UseMiddleware 扩展方法,此方法的参数列表中,最后一个是加了 params 关键字的 object 数组。这个就是用来传参数的,而且参数的个数是不确定的。传入的参数从中间件类的构造函数中接收。
二、通过依赖注入自动获取。虽然中间件类的构造函数可以接收注入对象,但是,不推荐在这里接收注入对象,因为这样会改变注入对象的生命周期。由于中间件类在运行阶段只实例化一次,故它的生命周期应与应用程序相等。所以,如果通过构造函数获取注入的话,由于注入对象长期存在引用,使得服务容器无法释放它。如果注入对象是用 AddTransient 方法添加到服务集合中,本应该每次使用后释放,但由于实例被引用,就会导致生命周期变得与应用程序同等,所以,不应该在中间件类的构造函数中来注入,而应该在 Invoke 或 InvokeAsync 方法中进行注入。
 
来来来,动手,咱们用实例来学习,效果会番十倍的。
先写一个中间件类。

public class DemoMiddleware
{
RequestDelegate _next;
double mx, my;

public DemoMiddleware(RequestDelegate next, double x, double y)
{
_next = next;
mx = x;
my = y;
}

public async Task InvokeAsync(HttpContext context)
{
double r = mx + my;
context.Response.Headers["Compute-Result"] = $"{mx} + {my} = {r}";
await _next(context);
}
}

注意啊各位,中间件类的构造函数,一般都要一个 RequestDelegate 的参数,干吗用的呢?就是让你可以调用下一个中间件,它代表的是链条上的下一个中间件。
两个 double 类型的参数才是我们真正要传的参数。在 InvokeAsync 方法中,我做了一个简单处理,把两个参数的值相加,然后通过 HTTP 头返回给客户端。
为了使下一个中间件能被调用,记得在 InvokeAsync 方法的最后调用一下 _next 字段。
 
现在,回到 Startup 类,找到 Configure 方法,我们使用刚刚定义的中间件类。

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
……

app.UseMiddleware<DemoMiddleware>(15.33d, 8.96d);
}

参数就是通过 UseMiddleware 方法来传递给中间件类的,有几个就传几个,该什么类型就什么类型,因为参数签名是 object 类型,有容乃大,它能兼容所有类型。
 
现在我们来测试一下。运行之后,你发现浏览器得到的是 404,对的,前面老周说了,中间件链条的最后一个中间件就是 404。我们没有向客户端回写任何内容,所以返回 404 太正常了。但是,代码其实是成功执行了的。
你可以打开抓包工具(比如 F12 工具里面就有),然后刷新一下,随后你去看一下返回消息的 Http 头。

看到了吧,说明中间件是执行了的。
 
下面,我们用依赖注入,在中间件中获取参数。

public class Demo2Middleware
{
RequestDelegate _next;

public Demo2Middleware(RequestDelegate next)
{
_next = next;
}

public async Task Invoke(HttpContext context, IHostingEnvironment env)
{
context.Response.Headers["app-name"] = env.ApplicationName;
context.Response.Headers["env-name"] = env.EnvironmentName;

await _next(context);
}
}

在 Invoke 方法中,第一个参数是 HttpContext,这个东东是必须的,然后后面我们获取一个 IHostingEnvironment,这个在应用程序初始化时由框架自动添加到服务容器中,它会自动注入到 Invoke 方法中。在上面代码中,我只是把应用名称和运行环境名称添加到 HTTP 头中。
 
随后在 Startup 类的 Configure 方法中,也用上这个中间件。

app.UseMiddleware<Demo2Middleware>();

运行之后,刷新浏览器,再抓包,你就看到在 Http Header 集合中多了几个东东。

爽吧,中间件也顺利执行了。
 
其实,你仔细一看就会发现,这个 Invoke / InvokeAsync 方法的注入方式与 Startup 类的 Configure 方法是一样的。Configure 方法的第一个参数是必须的,型类为 IApplicationBuilder,后面的参数就是注入的。
好了,向中间件传参数的两种方法都介绍完了。
最后,顺便说一下,“五一”假期前老周可能会直播一次 ASP.NET Core ,但不保证会开播,如果这几天没弄的话,就要等“五一”假期之后再开播。

最后编辑:
作者:
这个作者貌似有点懒,什么都没有留下。

留下一个回复