音效素材网提供各类素材,打造精品素材网站!

站内导航 站长工具 投稿中心 手机访问

音效素材

解读ASP.NET 5 & MVC6系列教程(6):Middleware详解
日期:2021-09-07 22:16:40   来源:脚本之家

在第1章项目结构分析中,我们提到Startup.cs作为整个程序的入口点,等同于传统的Global.asax文件,即:用于初始化系统级的信息(例如,MVC中的路由配置)。本章我们就来一一分析,在这里如何初始化这些系统级的信息。

新旧版本之间的Pipeline区别

ASP.NET 5和之前版本的最大区别是对HTTP Pipeline的全新重写,在之前的版本中,请求过滤器的通常是以HttpModule为模块组件,这些组件针对HttpApplication里定义的各个周期内的事件进行响应,从而用于实现认证、全局错误处理、日志等功能。传统的Form表单认证就是一个HTTPModuleHTTPModule不仅能够过滤Request请求,还可以和Response响应进行交互并修改。这些HTTPModule组件都继承于IHttpModule接口,而该接口是位于System.Web.dll中。

HttpModule代码不仅可以在Global.asax中的各事件周期中进行添加,还可以单独编译成类库并在web.config中进行注册。

新版的ASP.NET 5抛弃了重量级的System.Web.dll,相应地引入了Middleware的概念,Middleware的官方定义如下:

Pass through components that form a pipeline between a server and application to inspect, route, or modify request and response messages for a specific purpose.
在服务器和应用程序之间的管线Pipeline之间,针对特定的目的,穿插多个Middleware组件,从而对request请求和response响应进行检
查、路由、或修改。

该定义和传统的HttpModule以及HttpHandler特别像。

Middleware的注册和配置

在ASP.NET5中,request请求管线(Pipeline)的访问是在Startup类中进行的,该类时一个约定类,并且里面的ConfigureServices方法、Configure方法、以及相应的参数也是事先约定的,所以不能进行改动。

Middleware中的依赖处理:ConfigureServices方法

在ASP.NET5中的各种默认的Middleware中,都使用了依赖注入的功能,所以在使用Middleware中的功能时,需要提前将依赖注入所需要的类型及映射关系都注册到依赖注入管理系统中,即IServiceCollection集合,而ConfigureServices方法接收的就一个IServiceCollection类型的参数,该参数就是所有注册过类型的集合,通过原生的依赖注入组件进行管理(关于ASP.NET5中的依赖注入,我们会在单独章节中进行讲解),在该方法内,我们可以向该集合中添加新的类型和类型映射关系,示例如下:

// Add MVC services to the services container.
services.AddMvc();

示例中的代码用于向系统添加Mvc模块相关的Service类型以支撑MVC功能,该方法是一个扩展方法,用于在集合中添加与MVC相关的多个类型。

Middleware的注册和配置:Configure方法

Configure方法的签名如下:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerfactory)
{
 // ...
}

Configure方法接收了三个参数:IApplicationBuilder类型的参数用于构建整个应用程序的配置信息,IHostingEnvironment类的env参数用于访问系统环境变量相关的内容,ILoggerFactory类型的loggerfactory用于日志相关的内容处理,其中IApplicationBuilder类型的参数最为重要,该参数实例app上有一系列的扩展方法用于将各种Middleware注册到request请求管线(Pipeline)中。这种方式和之前ASP.NET中的HTTP管线的主要区别是:新版本中的组合模型替换了旧版本中的事件模型。这也就要求,在新版ASP.NET中,Middleware组件注册的顺序是非常重要的,因为后一个组件可能要使用到前一个组件,所以必须按照依赖的先后顺序进行注册,举例如下,当前MVC项目的模板代码示例如下:

// Add static files to the request pipeline.
app.UseStaticFiles();

// Add cookie-based authentication to the request pipeline.
app.UseIdentity();

// Add MVC to the request pipeline.
app.UseMvc(routes =>{ /*...*/});

示例中的UseStaticFilesUseIdentityUseMvc都是IApplicationBuilder上的扩展方法,在扩展方法中,都会通过调用扩展方法app.UseMiddleware方法,最终再调用app.Use方法来注册新的Middleware,该方法定义如下:

public interface IApplicationBuilder
{
 //...
 IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
}

通过代码,可以看出,middleware是Func<RequestDelegate, RequestDelegate>的一个实例,该Func接收一个RequestDelegate的参数,并返回一个RequestDelegate类型的值。RequestDelegate的源码如下:

public delegate Task RequestDelegate(HttpContext context);

通过源码,我们可以看出,RequestDelegate是一个委托函数,其接收HttpContext类型的实例,并返回一个Task类型的异步对象。也就是说RequestDelegate是一个可以返回自身RequestDelegate类型函数的函数,整个ASP.NET也就是利用这种方式构建了管线(Pipelien)的组成,在这里,每个middleware都链式到下一个middleware上,并在整个过程中可以对HttpConext对象进行修改或维护,当然,HttpContext中就包括了我们常操作的HttpRequestHttpResponse实例对象。

注意:HttpContextHttpRequestHttpResponse在ASP.NET 5中都是重新定义的新类型。

Middleware的定义

既然每个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 = @"<div id=""process"">页面处理时间:{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基类

虽然有约定方法,但有时候我们在开发的时候往往会犯迷糊,想不起来到底是什么样的约定,所以,在这里我们可以定义一个抽象基类,然后以后所有的Middleware在定义的时候都继承该抽象类并重载Invoke方法即可,从而可以避免约定忘记的问题。代码如下:

/// <summary>
/// 抽象基类
/// </summary>
public abstract class AbstractMiddleware
{
 protected RequestDelegate Next { get; set; }
 protected AbstractMiddleware(RequestDelegate next)
 {
  this.Next = next;
 }
 public abstract Task Invoke(HttpContext context);
}

/// <summary>
/// 示例Middleware
/// </summary>
public class DemoMiddleware : AbstractMiddleware
{
 public DemoMiddleware(RequestDelegate next) : base(next)
 {
 }
 public async override Task Invoke(HttpContext context)
 {
  Console.WriteLine("DemoMiddleware Start.");
  await Next.Invoke(context);
  Console.WriteLine("DemoMiddleware End.");
 }
}

使用方法和上面的一样。

终止链式调用或阻止所有的Middleware

在有些情况下,当然根据某些条件判断以后,可能不在需要继续往下执行下去了,而是想知己诶返回结果,那么你可以在你的Middleware里忽略对await next.Invoke(content);的调用,直接使用·Response.WriteAsync·方法输出内容。

另外,在有些情况下,你可能需要实现类似之前版本中的handler的功能,即不经常任何Pipeline直接对Response进行响应,新版ASP.NET里提供了一个run方法用于实现该功能,只需要在Configure方法里调用如下代码即可实现类似的内容输出。

app.Run(async context =>
{
 context.Response.ContentType = "text/html";
 await context.Response.WriteAsync("Hello World!");
});

关于ASP.NET 5 Runtime的内容,请访问:https://msdn.microsoft.com/en-us/magazine/dn913182.aspx

遗留问题

在Mvc项目中,所有的依赖注入类型都是通过IServiceProvider实例来获取的,目前可以通过以下形式获取该实例:

var services = Context.RequestServices; // Controller中
var services = app.ApplicationServices; // Startup中

获取了该实例以后,即可通过如下方法来获取某个类型的对象:

var controller = (AccountController)services.GetService(typeof(AccountController));
// 要判断获取到的对象是否为null

如果你引用了Microsoft.Framework.DependencyInjection命名空间的话,还可以使用如下三种扩展方法:

var controller2 = (AccountController)services.GetService<AccountController>(); 
// 要判断获取到的对象是否为null

//如下两种方式,如果获取到的AccountController实例为null的话,就会字段抛异常,而不是返回null
var controller3 = (AccountController)services.GetRequiredService(typeof(AccountController));
var controller4 = (AccountController)services.GetRequiredService<AccountController>();

那么问题来了?如何不在Startup和Controller里就可以获取到HttpContext和IApplicationBuilder实例以便使用这些依赖注入服务?

如何获取IApplicationBuilder实例?
答案:在Startup里将IApplicationBuilder实例保存在一个单例中的变量上,后期全站就可以使用了。

如何获取HttpContext实例?
答案:参考依赖注入章节的普通类的依赖注入

引用:http://www.mikesdotnetting.com/article/269/asp-net-5-middleware-or-where-has-my-httpmodule-gone

    您感兴趣的教程

    在docker中安装mysql详解

    本篇文章主要介绍了在docker中安装mysql详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编...

    详解 安装 docker mysql

    win10中文输入法仅在桌面显示怎么办?

    win10中文输入法仅在桌面显示怎么办?

    win10系统使用搜狗,QQ输入法只有在显示桌面的时候才出来,在使用其他程序输入框里面却只能输入字母数字,win10中...

    win10 中文输入法

    一分钟掌握linux系统目录结构

    这篇文章主要介绍了linux系统目录结构,通过结构图和多张表格了解linux系统目录结构,感兴趣的小伙伴们可以参考一...

    结构 目录 系统 linux

    PHP程序员玩转Linux系列 Linux和Windows安装

    这篇文章主要为大家详细介绍了PHP程序员玩转Linux系列文章,Linux和Windows安装nginx教程,具有一定的参考价值,感兴趣...

    玩转 程序员 安装 系列 PHP

    win10怎么安装杜比音效Doby V4.1 win10安装杜

    第四代杜比®家庭影院®技术包含了一整套协同工作的技术,让PC 发出清晰的环绕声同时第四代杜比家庭影院技术...

    win10杜比音效

    纯CSS实现iOS风格打开关闭选择框功能

    这篇文章主要介绍了纯CSS实现iOS风格打开关闭选择框,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作...

    css ios c

    Win7如何给C盘扩容 Win7系统电脑C盘扩容的办法

    Win7如何给C盘扩容 Win7系统电脑C盘扩容的

    Win7给电脑C盘扩容的办法大家知道吗?当系统分区C盘空间不足时,就需要给它扩容了,如果不管,C盘没有足够的空间...

    Win7 C盘 扩容

    百度推广竞品词的投放策略

    SEM是基于关键词搜索的营销活动。作为推广人员,我们所做的工作,就是打理成千上万的关键词,关注它们的质量度...

    百度推广 竞品词

    Visual Studio Code(vscode) git的使用教程

    这篇文章主要介绍了详解Visual Studio Code(vscode) git的使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...

    教程 Studio Visual Code git

    七牛云储存创始人分享七牛的创立故事与

    这篇文章主要介绍了七牛云储存创始人分享七牛的创立故事与对Go语言的应用,七牛选用Go语言这门新兴的编程语言进行...

    七牛 Go语言

    Win10预览版Mobile 10547即将发布 9月19日上午

    微软副总裁Gabriel Aul的Twitter透露了 Win10 Mobile预览版10536即将发布,他表示该版本已进入内部慢速版阶段,发布时间目...

    Win10 预览版

    HTML标签meta总结,HTML5 head meta 属性整理

    移动前端开发中添加一些webkit专属的HTML5头部标签,帮助浏览器更好解析HTML代码,更好地将移动web前端页面表现出来...

    移动端html5模拟长按事件的实现方法

    这篇文章主要介绍了移动端html5模拟长按事件的实现方法的相关资料,小编觉得挺不错的,现在分享给大家,也给大家...

    移动端 html5 长按

    HTML常用meta大全(推荐)

    这篇文章主要介绍了HTML常用meta大全(推荐),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参...

    cdr怎么把图片转换成位图? cdr图片转换为位图的教程

    cdr怎么把图片转换成位图? cdr图片转换为

    cdr怎么把图片转换成位图?cdr中插入的图片想要转换成位图,该怎么转换呢?下面我们就来看看cdr图片转换为位图的...

    cdr 图片 位图

    win10系统怎么录屏?win10系统自带录屏详细教程

    win10系统怎么录屏?win10系统自带录屏详细

    当我们是使用win10系统的时候,想要录制电脑上的画面,这时候有人会想到下个第三方软件,其实可以用电脑上的自带...

    win10 系统自带录屏 详细教程

    + 更多教程 +
    ASP编程JSP编程PHP编程.NET编程python编程