既然每个middleare都是Func<RequestDelegate, RequestDelegate>的一个实例,那是不是Middleware的定义要满足一个规则?是继承于一个抽象基类还是借口?通过翻查相关的代码,我们看到,Middleware是基于约定的形式来定义的,具体约定规则如下:
构造函数的第一个参数必须是处理管线中的下一个处理函数,即RequestDelegate;必须有一个 Invoke 函数, 并接受上下文参数(即HttpContent), 然后返回 Task;
示例如下:
public class MiddlewareName { RequestDelegate _next; public MiddlewareName(RequestDelegate next) { _next = next;// 接收传入的RequestDelegate实例 } public async Task Invoke(HttpContext context) { // 处理代码,如处理context.Request中的内容 Console.WriteLine("Middleware开始处理"); await _next(context); Console.WriteLine("Middleware结束处理"); // 处理代码,如处理context.Response中的内容 } }
通过该模板代码可以看到,首先一个Middleware的构造函数要接收一个RequestDelegate的实例,先保存在一个私有变量里,然后通过调用Invoke方法(并接收HttpContent实例)并返回一个Task,并且在调用Invoke的方法中,要通过await _next(context);语句,链式到下一个Middleware上,我们的处理代码主要就是在链式语句的前后执行相关的代码。
举个例子,如果我们要想记录页面的执行时间,首先,我们先定义一个TimeRecorderMiddleware,代码如下:
public class TimeRecorderMiddleware { RequestDelegate _next; public TimeRecorderMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { var sw = new Stopwatch(); sw.Start(); await _next(context); var newDiv = @"<divprocess"">页面处理时间:{0} 毫秒</div></body>"; var text = string.Format(newDiv, sw.ElapsedMilliseconds); await context.Response.WriteAsync(text); } }
Middleware的注册有很多种方式,如下是实例型注册代码:
app.Use(next => new TimeRecorderMiddleware(next).Invoke);
或者,你也可以使用UseMiddleware扩展方法进行注册,示例如下:
app.UseMiddleware<TimeRecorderMiddleware>(); //app.UseMiddleware(typeof(TimeRecorderMiddleware)); 两种方式都可以
当然,你也可以定义一个自己的扩展方法用于注册该Middleware,代码如下:
public static IApplicationBuilder UseTimeRecorderMiddleware(this IApplicationBuilder app) { return app.UseMiddleware<TimeRecorderMiddleware>(); }
最后在Startup类的Configure方法内进行注册,代码如下:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory) { app.UseTimeRecorderMiddleware(); // 要放在前面,以便进行统计,如果放在Mvc后面的话,就统计不了时间了。 // 等等 }
编译,重启,并访问页面,在页面的底部即可看到页面的运行时间提示内容。
常用Middleware功能的使用
app.UseErrorPage()
在IHostingEnvironment.EnvironmentName为Development的情况下,才显示错误信息,并且错误信息的显示种类,可以通过额外的ErrorPageOptions参数来设定,可以设置全部显示,也可以设置只显示Cookies、Environment、ExceptionDetails、Headers、Query、SourceCode SourceCodeLineCount中的一种或多种。
app.UseErrorHandler("/Home/Error")
捕获所有的程序异常错误,并将请求跳转至指定的页面,以达到友好提示的目的。
app.UseStaticFiles()
开启静态文件也能走该Pipeline管线处理流程的功能。
app.UseIdentity()
开启以cookie为基础的ASP.NET identity认证功能,以支持Pipeline请求处理。
直接使用委托定义Middleware的功能
由于Middleware是Func<RequestDelegate, RequestDelegate>委托类型的实例,所以我们也可以不必定义一个单独的类,在Startup类里,使用委托调用的方式就可以了,示例如下:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory) { app.Use(new Func<RequestDelegate, RequestDelegate>(next => content => Invoke(next, content))); // 其它 } // 注意Invoke方法的参数 private async Task Invoke(RequestDelegate next, HttpContext content) { Console.WriteLine("初始化组件开始"); await next.Invoke(content); Console.WriteLine("管道下步执行完毕"); }
做个简便的Middleware基类