什么是中间件

中间件是一种装配到应用管道,以处理请求和响应的组件。每个中间件:

  • 选择是否将请求传递到管道中的下一个中间件。
  • 可在管道中的下一个中间件前后执行。

ASP.NET Core请求管道包含一系列请求委托,依次调用。工作原理:

PS:类似于ASP.NET里的Handler(处理程序)和Module(模块)。

HTTP模块和处理程序的工作原理:

创建中间件管道

Run委托不会收到next参数。第一个Run委托始终为终端,用于终止管道。Run是一种约定。

public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Run(async context =>
{
await context.Response.WriteAsync("Hello, World!");
});
}
}

Use将多个请求委托链接在一起。next参数表示管道中的下一个委托。可通过不调用next参数使用管道短路。通常可在上一个委托前后执行操作:

public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
// Do work that doesn't write to the Response.
await next.Invoke();
// Do logging or other work that doesn't write to the Response.
}); app.Run(async context =>
{
await context.Response.WriteAsync("Hello from 2nd delegate.");
});
}
}

警告:在向客户端发送Response后,不要再调用next.Invoke。Response启动后,针对HttpResponse的修改会引起异常。调用next后写入Response正文:

  • 可能导致违反协议。例如,写入的长度超过规定的Content-Length。
  • 可能损坏正文格式。例如,向CSS文件中写入HTML页脚。

HasStarted是一个有用的提示,指示是否已发送标头或已写入正文。

中间件顺序

中间件是通过请求管道逐一执行的,通过Startup.Configure方法里添加中间件的顺序来定义了请求管道的顺序。

如上图所示,自定义的中间件放在Authorization中间件和Endpoint之间。

以下是ASP.NET Core项目默认配置的中间件顺序:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
} app.UseHttpsRedirection();
app.UseStaticFiles();
// app.UseCookiePolicy(); app.UseRouting();
// app.UseRequestLocalization();
// app.UseCors(); app.UseAuthentication();
app.UseAuthorization();
// app.UseSession(); app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}

中间件分支

当我们需要对某些请求进行特定处理时,可以通过创建管道分支来实现。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>");
});
}
}

MapWhen是用于对特定谓词的结果来创建请求管道分支。相比于Map(只能对针对特定的Url)更加高级。

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>");
});
}
}

UseWhen也是用于对特定谓词的结果来创建请求管道分支。但与MapWhen不同的是,如果这个分支不发生短路或包含终端中间件,则会重新加入主管道。

public class Startup
{
private readonly ILogger<Startup> _logger; public Startup(ILogger<Startup> logger)
{
_logger = logger;
} private void HandleBranchAndRejoin(IApplicationBuilder app)
{
app.Use(async (context, next) =>
{
var branchVer = context.Request.Query["branch"];
_logger.LogInformation("Branch used = {branchVer}", branchVer); // Do work that doesn't write to the Response.
await next();
// Do other work that doesn't write to the Response.
});
} public void Configure(IApplicationBuilder app)
{
app.UseWhen(context => context.Request.Query.ContainsKey("branch"),
HandleBranchAndRejoin); app.Run(async context =>
{
await context.Response.WriteAsync("Hello from main pipeline.");
});
}
}

自定义中间件

下面以多语言版本功能为例,介绍如何编写自定义中间件。

多语言版本功能指根据所在区域,以所在区域的语言显示网站内容。例如在中国以汉字显示、在美国以英文显示、在俄罗斯以俄文显示。

下面代码是直接在Startup.Configure编写,通过参数culture手动指定语言版本,例如请求https://localhost:5001/?culture=zh-cn

public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.Use(async (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
await next();
}); app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
}); }
}

一般来说,编写自定义中间件使用类,既符合单一职责原则,也更方便管理。

using Microsoft.AspNetCore.Http;
using System.Globalization;
using System.Threading.Tasks; namespace Culture
{
public class RequestCultureMiddleware
{
private readonly RequestDelegate _next; public RequestCultureMiddleware(RequestDelegate next)
{
_next = next;
} public async Task InvokeAsync(HttpContext context)
{
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);
}
}
}

中间件类必须满足:

  • RequestDelegate类型参数的公共构造函数。
  • 有名字为InvokeInvokeAsync的方法,并且方法要满足:
    • 返回值是Task
    • 方法参数第一个是HttpContext类型。

在实际开发中,中间件会用到其他服务,由于中间件是在应用启动时创建的,生命周期是Singleton的,不与其他依赖注入的服务共享生命周期。如果需要使用依赖注入,可以通过InvokeInvokeAsync方法注入。

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 = 1000;
await _next(httpContext);
}
}

最后,我们习惯性在Startup.Configure中,通过扩展方法来注册中间件。

using Microsoft.AspNetCore.Builder;

namespace Culture
{
public static class RequestCultureMiddlewareExtensions
{
public static IApplicationBuilder UseRequestCulture(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<RequestCultureMiddleware>();
}
}
} public class Startup
{
public void Configure(IApplicationBuilder app)
{
app.UseRequestCulture(); app.Run(async (context) =>
{
await context.Response.WriteAsync(
$"Hello {CultureInfo.CurrentCulture.DisplayName}");
});
}
}

参考资料

[ASP.NET Core开发实战]基础篇03 中间件的更多相关文章

  1. [ASP.NET Core开发实战]基础篇02 依赖注入

    ASP.NET Core的底层机制之一是依赖注入(DI)设计模式,因此要好好掌握依赖注入的用法. 什么是依赖注入 我们看一下下面的例子: public class MyDependency { pub ...

  2. [ASP.NET Core开发实战]基础篇01 Startup

    Startup,顾名思义,就是启动类,用于配置ASP.NET Core应用的服务和请求管道. Startup有两个主要作用: 通过ConfigureServices方法配置应用的服务.服务是一个提供应 ...

  3. [ASP.NET Core开发实战]基础篇06 配置

    配置,是应用程序很重要的组成部分,常常用于提供信息,像第三方应用登录钥匙.上传格式与大小限制等等. ASP.NET Core提供一系列配置提供程序读取配置文件或配置项信息. ASP.NET Core项 ...

  4. [ASP.NET Core开发实战]基础篇05 服务器

    什么是服务器 服务器指ASP.NET Core应用运行在操作系统上的载体,也叫Web服务器. Web服务器实现侦听HTTP请求,并以构建HttpContext的对象发送给ASP.NET Core应用. ...

  5. [ASP.NET Core开发实战]基础篇04 主机

    主机定义 主机是封闭应用资源的对象. 设置主机 主机通常由 Program 类中的代码配置.生成和运行. HTTP项目(ASP.NET Core项目)创建泛型主机: public class Prog ...

  6. 2月送书福利:ASP.NET Core开发实战

    大家都知道我有一个公众号“恰童鞋骚年”,在公众号2020年第一天发布的推文<2020年,请让我重新介绍我自己>中,我曾说到我会在2020年中每个月为所有关注“恰童鞋骚年”公众号的童鞋们送一 ...

  7. [ASP.NET Core开发实战]开篇词

    前言 本系列课程文章主要是学习官方文档,再输出自己学习心得,希望对你有所帮助. 课程大纲 本系列课程主要分为三个部分:基础篇.实战篇和部署篇. 希望通过本系列课程,能让大家初步掌握使用ASP.NET ...

  8. ASP.NET Core 2.2 基础知识(二) 中间件

    中间件是一种装配到应用管道以处理请求和相应的软件.每个软件都可以: 1.选择是否将请求传递到管道中的下一个组件; 2.可在调用管道中的下一个组件前后执行工作. 管道由 IApplicationBuil ...

  9. 《ASP.NET Core项目开发实战入门》带你走进ASP.NET Core开发

    <ASP.NET Core项目开发实战入门>从基础到实际项目开发部署带你走进ASP.NET Core开发. ASP.NET Core项目开发实战入门是基于ASP.NET Core 3.1 ...

随机推荐

  1. Hadoop学习问题记录之基础篇

    目的 记录学习hadoop过程中遇到的基础问题,无关大小.无关困扰时间长短. 问题一 全分布式环境中运行mapred程序,报异常:java.net.NoRouteToHostException: 没有 ...

  2. IDEA生成MyBatis文件

    IDEA 逆向 MyBatis 工程时,不像支持 Hibernate 那样有自带插件,需要集成第三方的 MyBatis Generator. MyBatis Generator的详细介绍 http:/ ...

  3. 【mysql数据库基础】

    基础:·数据库的本质是一个文件·行---记录·列---字段·RDBMS是一个程序·SQL是结构化的查询语言·MYSQL是一个数据库软件,可以通过SQL操作MYSQL数据库·SQL语句不区分大小写·学了 ...

  4. python爬虫学习05-爬取图片

    python爬虫学习05-爬取图片 确定要爬取的网址:https://shenan.tuchong.com/20903415/#image309854686 要爬取的内容:使用浏览器插件xpath对图 ...

  5. JVM系列之:JIT中的Virtual Call接口

    目录 简介 最常用的接口List 多个List的调用 不一样的List调用 总结 简介 上一篇文章我们讲解了Virtual Call的定义并举例分析了Virtual Call在父类和子类中的优化. J ...

  6. Python入门看这些,最详细学习书籍推荐

    随着人工智能以及脚本开发火热,Python已经被推上一个非常火热的巅峰! 那么,想要学习Python却又不知道从哪里开始的朋友,看这里呀~ Python在整个编程语言来说,是比较容易上手,而且“见效” ...

  7. [HNOI2009]最小圈 题解

    题目大意 给你一个有向图,求出图中环的平均值的最小值 环的平均值定义:环中所有的边权和/环中点数量 思路 看到使平均值最大或最小,可以考虑分数规划 分数规划用于解决一些要让平均值最大或最小的问题 具体 ...

  8. 2020-07-14:es用过冷热分离吗?假如现在有些数据热变冷,有些数据冷变热,怎么解决?

    福哥答案2020-07-14: 热变冷: 有x台机器tag设置为hot. 有y台机器tag设置为cool. hot集群中只存最近两天的. 有一个定时任务每天将前一天的索引标记为cool. es看到有新 ...

  9. 如何将返回的JSon字符串用MAP格式读取

    语法是这样: ObjectMapper mapper = new ObjectMapper(); Map resultMap=null; resultMap = mapper.readValue(in ...

  10. python爬虫抖音 个人资料 仅供学习参考 切勿用于商业

    本文仅供学习参考 切勿用于商业 本次爬取使用fiddler+模拟器(下载抖音APP)+pycharm 1. 下载最新版本的fiddler(自行百度下载),以及相关配置 1.1.依次点击,菜单栏-Too ...