1.前言

整个HTTP Request请求跟HTTP Response返回结果之间的处理流程是一个请求管道(request pipeline)。而中间件(middleware)则是一种装配到请求管道以处理请求和响应的组件。每个组件:
可选择是否将请求传递到管道中的下一个组件。
可在管道中的下一个组件前后执行工作。
中间件(middleware)处理流程如下图所示:

2.使用中间件

ASP.NET Core请求管道中每个中间件都包含一系列的请求委托(request delegates)来处理每个HTTP请求,依次调用。请求委托通过使用IApplicationBuilder类型的Run、Use和Map扩展方法在Strartup.Configure方法中配置。下面我们通过配置Run、Use和Map扩展方法示例来了解下中间件。

2.1 Run

public class Startup
{
public void Configure(IApplicationBuilder app)
{
//第一个请求委托Run
app.Run(async context =>//内嵌匿名方法
{
await context.Response.WriteAsync("Hello, World!");
});
//第二个请求委托Run
app.Run(async context =>//内嵌匿名方法
{
await context.Response.WriteAsync("Hey, World!");
});
}
}

响应结果:

由上述代码可知,Run方法指定为一个内嵌匿名方法(称为并行中间件,in-line middleware),而内嵌匿名方法中并没有指定执行下一个请求委托,这一个过程叫管道短路,而该中间件又叫“终端中间件”(terminal middleware),因为它阻止中间件下一步处理请求。所以在Run第一个请求委托的时候就已经终止请求,并没有执行第二个请求委托直接返回Hello, World!输出文本。而根据官网解释,Run是一种约定,有些中间件组件可能会暴露他们自己的Run方法,而这些方法只能在管道末尾处运行(也就是说Run方法只在中间件执行最后一个请求委托时才使用)。

2.2 Use

public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
context.Response.ContentType = "text/plain; charset=utf-8";
await context.Response.WriteAsync("进入第一个委托 执行下一个委托之前\r\n");
//调用管道中的下一个委托
await next.Invoke();
await context.Response.WriteAsync("结束第一个委托 执行下一个委托之后\r\n");
});
app.Run(async context =>
{
await context.Response.WriteAsync("进入第二个委托\r\n");
await context.Response.WriteAsync("Hello from 2nd delegate.\r\n");
await context.Response.WriteAsync("结束第二个委托\r\n");
});
}

响应结果:

由上述代码可知,Use方法将多个请求委托链接在一起。而next参数表示管道中的下一个委托。如果不调用next参数调用下一个请求委托则会使管道短路。比如,一个授权(authorization)中间件只有通过身份验证之后才能调用下一个委托,否则它就会被短路,并返回“Not Authorized”的响应。所以应尽早在管道中调用异常处理委托,这样它们就能捕获在管道的后期阶段发生的异常。

2.3 Map和MapWhen

●Map:Map扩展基于请求路径创建管道分支。
●MapWhen:MapWhen扩展基于请求条件创建管道分支。
Map示例:

public class Startup
{
private static void HandleMapTest1(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 1");
});
} private static void HandleMapTest2(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map Test 2");
});
} public void Configure(IApplicationBuilder app)
{
app.Map("/map1", HandleMapTest1);
app.Map("/map2", HandleMapTest2); app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});
}
}

下面表格使用前面的代码显示来自http://localhost:5001的请求和响应。

请求

响应

localhost:5001

Hello from non-Map delegate.

localhost:5001/map1

Map Test 1

localhost:5001/map2

Map Test 2

localhost:5001/map3

Hello from non-Map delegate.

由上述代码可知,Map方法将从HttpRequest.Path中删除匹配的路径段,并针对每个请求将该路径追加到HttpRequest.PathBase。也就是说当我们在浏览器上输入map1请求地址的时候,系统会执行map1分支管道输出其请求委托信息,同理执行map2就会输出对应请求委托信息。
MapWhen示例:

public class Startup
{
private static void HandleBranch(IApplicationBuilder app)
{
app.Run(async context =>
{
var branchVer = context.Request.Query["branch"];
await context.Response.WriteAsync($"Branch used = {branchVer}");
});
}
public void Configure(IApplicationBuilder app)
{
app.MapWhen(context => context.Request.Query.ContainsKey("branch"),
HandleBranch);
app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate. <p>");
});
}
}

下面表格使用前面的代码显示来自http://localhost:5001的请求和响应。

请求

响应

http://localhost:5001

Hello from non-Map delegate. <p>

https://localhost:5001/?branch=master

Branch used = master

由上述代码可知,MapWhen是基于branch条件而创建管道分支的,我们在branch条件上输入master就会创建其对应管道分支。也就是说,branch条件上输入任何一个字符串条件,都会创建一个新的管理分支。
而且还Map支持嵌套,例如:

public void Configure(IApplicationBuilder app)
{
app.Map("/level1", level1App => {
level1App.Map("/level2a", level2AApp => {
// "/level1/level2a" processing
});
level1App.Map("/level2b", level2BApp => {
// "/level1/level2b" processing
});
});
}

还可同时匹配多个段:

public class Startup
{
private static void HandleMultiSeg(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Map multiple segments.");
});
}
public void Configure(IApplicationBuilder app)
{
app.Map("/map1/seg1", HandleMultiSeg); app.Run(async context =>
{
await context.Response.WriteAsync("Hello from non-Map delegate.");
});
}
}

3.顺序

向Startup.Configure方法添加中间件组件的顺序定义了在请求上调用它们的顺序,以及响应的相反顺序。此排序对于安全性、性能和功能至关重要。
以下Startup.Configure方法将为常见应用方案添加中间件组件:
●异常/错误处理(Exception/error handling)
●HTTP严格传输安全协议(HTTP Strict Transport Security Protocol)
●HTTPS重定向(HTTPS redirection)
●静态文件服务器(Static file server)
●Cookie策略实施(Cookie policy enforcement)
●身份验证(Authentication)
●会话(Session)
●MVC
请看如下代码:

public void Configure(IApplicationBuilder app)
{
if (env.IsDevelopment())
{
// When the app runs in the Development environment:
// Use the Developer Exception Page to report app runtime errors.
// Use the Database Error Page to report database runtime errors.
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
// When the app doesn't run in the Development environment:
// Enable the Exception Handler Middleware to catch exceptions
// thrown in the following middlewares.
// Use the HTTP Strict Transport Security Protocol (HSTS)
// Middleware.
app.UseExceptionHandler("/Error");
app.UseHsts();
}
// Return static files and end the pipeline.
app.UseStaticFiles();
// Authenticate before the user accesses secure resources.
app.UseAuthentication();
}

从上述示例代码中,每个中间件扩展方法都通过Microsoft.AspNetCore.Builder命名空间在 IApplicationBuilder上公开。但是为什么我们要按照这个顺序去添加中间件组件呢?下面我们挑几个中间件来了解下。
UseExceptionHandler(异常/错误处理)是添加到管道的第一个中间件组件。因此我们可以捕获在应用程序调用中发生的任何异常。那为什么要将异常/错误处理放在第一位呢?那是因为这样我们就不用担心因前面中间件短路而导致捕获不到整个应用程序所有异常信息。
UseStaticFiles(静态文件)中间件在管道中提前调用,方便它可以处理请求和短路,而无需通过剩余中间组件。也就是说静态文件中间件不用经过UseAuthentication(身份验证)检查就可以直接访问,即可公开访问由静态文件中间件服务的任何文件,包括wwwroot下的文件。
UseAuthentication(身份验证)仅在MVC选择特定的Razor页面或Controller和Action之后才会发生。
经过上面描述,大家都了解中间件顺序的重要性了吧。

4.编写中间件(重点)

虽然ASP.NET Core为我们提供了一组丰富的内置中间件组件,但在某些情况下,你可能需要写入自定义中间件。

4.1中间件类

下面我们自定义一个查询当前区域性的中间件:

public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use((context, next) =>
{
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
// Call the next delegate/middleware in the pipeline
return next();
});
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});
}
}

可通过传入区域性参数条件进行测试。例如http://localhost:7997/?culture=zh、http://localhost:7997/?culture=en。
但是如果每个自定义中间件都在Startup.Configure方法中编写如上一大堆代码,那么对于程序来说,将是灾难性的(不利于维护和调用)。为了更好管理代码,我们应该把内嵌匿名方法封装到新建的自定义类(示例自定义RequestCultureMiddleware类)里面去:

public class RequestCultureMiddleware
{
private readonly RequestDelegate _next;
public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
context.Response.ContentType = "text/plain; charset=utf-8";
var cultureQuery = context.Request.Query["culture"];
if (!string.IsNullOrWhiteSpace(cultureQuery))
{
var culture = new CultureInfo(cultureQuery);
CultureInfo.CurrentCulture = culture;
CultureInfo.CurrentUICulture = culture;
}
// Call the next delegate/middleware in the pipeline
await _next(context);
}
}

通过Startup.Configure方法调用中间件:

public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseMiddleware<RequestCultureMiddleware>();
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});
}
}

4.2中间件扩展方法

Startup.Configure方法调用中间件设置可以通过自定义的扩展方法将其公开(调用内置IApplicationBuilder公开中间件)。示例创建一个RequestCultureMiddlewareExtensions扩展类并通过IApplicationBuilder公开:

public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}

再通过Startup.Configure方法调用中间件:

public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseRequestCulture();
app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});
}
}

响应结果:

通过委托构造中间件,应用程序在运行时创建这个中间件,并将它添加到管道中。这里需要注意的是,中间件的创建是单例的,每个中间件在应用程序生命周期内只有一个实例。那么问题来了,如果我们业务逻辑需要多个实例时,该如何操作呢?请继续往下看。

6.按每次请求创建依赖注入(DI)

在中间件的创建过程中,内置的IOC容器会为我们创建一个中间件实例,并且整个应用程序生命周期中只会创建一个该中间件的实例。通常我们的程序不允许这样的注入逻辑。其实,我们可以把中间件理解成业务逻辑的入口,真正的业务逻辑是通过Application Service层实现的,我们只需要把应用服务注入到Invoke方法中即可。ASP.NET Core为我们提供了这种机制,允许我们按照请求进行依赖的注入,也就是每次请求创建一个服务。示例:

public class CustomMiddleware
{
private readonly RequestDelegate _next;
public CustomMiddleware(RequestDelegate next)
{
_next = next;
}
// IMyScopedService is injected into Invoke
public async Task Invoke(HttpContext httpContext, IMyScopedService svc)
{
svc.MyProperty();
await _next(httpContext);
}
}
public static class CustomMiddlewareExtensions
{
public static IApplicationBuilder UseCustomMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CustomMiddleware>();
}
}
public interface IMyScopedService
{
void MyProperty(decimal input);
}
public class MyScopedService : IMyScopedService
{
public void MyProperty(decimal input)
{
Console.WriteLine("MyProperty is " + input);
}
}
public void ConfigureServices(IServiceCollection services)
{
//注入DI服务
services.AddScoped<IMyScopedService, MyScopedService>();
}

响应结果:

参考文献:
ASP.NET Core中间件
写入自定义ASP.NET Core中间件

(4)ASP.NET Core 中间件的更多相关文章

  1. ASP.NET Core 中间件Diagnostics使用

    ASP.NET Core 中间件(Middleware)Diagnostics使用.对于中间件的介绍可以查看之前的文章ASP.NET Core 开发-中间件(Middleware). Diagnost ...

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

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

  3. [转]ASP.NET Core 中间件详解及项目实战

    本文转自:http://www.cnblogs.com/savorboard/p/5586229.html 前言 在上篇文章主要介绍了DotNetCore项目状况,本篇文章是我们在开发自己的项目中实际 ...

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

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

  5. ASP.NET Core中间件实现分布式 Session

    1. ASP.NET Core中间件详解 1.1. 中间件原理 1.1.1. 什么是中间件 1.1.2. 中间件执行过程 1.1.3. 中间件的配置 1.2. 依赖注入中间件 1.3. Cookies ...

  6. ASP.NET Core 入门教程 9、ASP.NET Core 中间件(Middleware)入门

    一.前言 1.本教程主要内容 ASP.NET Core 中间件介绍 通过自定义 ASP.NET Core 中间件实现请求验签 2.本教程环境信息 软件/环境 说明 操作系统 Windows 10 SD ...

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

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

  8. ASP.NET Core 中间件基本用法

    ASP.NET Core 中间件 ASP.NET Core的处理流程是一个管道,而中间件是装配到管道中的用于处理请求和响应的组件.中间件按照装配的先后顺序执行,并决定是否进入下一个组件.中间件管道的处 ...

  9. ASP.NET Core 中间件 - ASP.NET Core 基础教程 - 简单教程,简单编程

    原文:ASP.NET Core 中间件 - ASP.NET Core 基础教程 - 简单教程,简单编程 ASP.NET Core 中间件 上一章节中,我们我们有讲到 Startup 类中的 Confi ...

随机推荐

  1. [Javascript] Wrap fireEvent with fireEventAsync

    The idea is wrap a object with all its function methods and add some additional handling into a new ...

  2. Ubuntu下编译Android JNI实例全过程

    第一步:保证make和gcc可用 在shell中输入make-v.不报错就是对的.(可參考http://wenku.baidu.com/view/d87586c24028915f804dc24a.ht ...

  3. postgres源码目录结构

    1.第一级目录介绍 |_postgres |_aclocal.m4------------config用的文件的一部分 |_config----------------config文件目录 |_con ...

  4. Codeforces Round #335 (Div. 2) 606B Testing Robots(模拟)

    B. Testing Robots time limit per test 2 seconds memory limit per test 256 megabytes input standard i ...

  5. HDU 1022 Train Problem I (数据结构 —— 栈)

    Problem Description As the new term comes, the Ignatius Train Station is very busy nowadays. A lot o ...

  6. 【php】读取&quot;文件列表&quot;按时间倒序显示,并递归显示各层文件夹、!

    思路: 1.读取该php所在文件夹的文件列表,用"改动时间.文件名称"做键值对,塞入数组.对"改动时间"倒序.(貌似不能直接按时间倒序读取文件列表,此处为间接方 ...

  7. python安装easy_install和pip

    1 安装easy_install https://pypi.python.org/pypi/setuptools 下载setuptools 执行python setup.py install就安装成功 ...

  8. Pattern: Microservice Architecture

    Microservice Architecture pattern http://microservices.io/patterns/microservices.html Context You ar ...

  9. LIS(最长上升子序列)的三种经典求法

    求最长上升子序列的三种经典方案: 给定一个长度为 \(N\) 的数列,求它数值单调递增的子序列长度最大为多少.即已知有数列 \(A\) , \(A=\{A_1,A_2....A_n\}\) ,求 \( ...

  10. finally{} 代码块

    package Exception; /* * finally{}代码块 * * finally{]代码块是必须要被执行的,不管异常是否处理成功,该代码块里面的代码体都会被执行, */ public ...