循序渐进学.Net Core Web Api开发系列【13】:中间件(Middleware)
系列目录
本系列涉及到的源码下载地址:
一、概述
本篇介绍如何使用中间件(Middleware)。
二、初步演练
先写几个中间件
public class DemoAMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; public DemoAMiddleware(RequestDelegate next, ILogger<DemoAMiddleware> logger) { _next = next; _logger = logger; } public async Task Invoke(HttpContext context) { _logger.LogInformation("(1) DemoAMiddleware.Invoke front"); await _next(context); _logger.LogInformation("[1] DemoAMiddleware:Invoke back"); } } public class DemoBMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; public DemoBMiddleware(RequestDelegate next, ILogger<DemoBMiddleware> logger) { _next = next; _logger = logger; } public async Task Invoke(HttpContext context) { _logger.LogInformation("(2) DemoBMiddleware.Invoke part1"); await _next(context); _logger.LogInformation("[2] DemoBMiddleware:Invoke part2"); } } public class RequestRecordMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; public RequestRecordMiddleware(RequestDelegate next, ILogger<RequestRecordMiddleware> logger) { _next = next; _logger = logger; } public async Task Invoke(HttpContext context) { _logger.LogInformation("(3) RequestRecordMiddleware.Invoke"); String URL = context.Request.Path.ToString(); _logger.LogInformation($"URL : {URL}"); await _next(context); _logger.LogInformation("[3] RequestRecordMiddleware:Invoke next"); _logger.LogInformation($"StatusCode : {context.Response.StatusCode}"); } }
以上中间件前两个没有做什么正经工作,就打印一些日志信息,第三个干了一点工作,它打印了用户输入的url,同时打印了返回给客户端的状态码。
要使中间件工作,需要启用中间件。
public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddMvc(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { app.UseUnifyException(); app.UseMiddleware<DemoAMiddleware>(); app.UseMiddleware<DemoBMiddleware>(); app.UseMiddleware<RequestRecordMiddleware>(); app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); }
通过扩展方法,我们对中间件的启用代码进行改造:
public static class RequestRecordMiddlewareExtensions { public static IApplicationBuilder UseRequestRecord(this IApplicationBuilder builder) { if (builder == null) { throw new ArgumentNullException("builder is null"); } return builder.UseMiddleware<RequestRecordMiddleware>(); } }
此时启用代码由:app.UseMiddleware<RequestRecordMiddleware>();
可以修改为: app.UseRequestRecord();
实现效果没有变化。可见下面代码都是中间件的使用。
app.UseStaticFiles(); app.UseMvcWithDefaultRoute();
我的理解,中间件类似车间流水线上的工人,操作的零件就是HttpContext,每个人负责两道工序,我把它们定义为“前道工序”和“后道工序”,通过代码 _next(context); 把两道工序隔离开,处理的顺序需要特别注意,按照中间件注册的顺序依次处理“前道工序”,处理完成后,再按相反的顺序处理“后道工序”,如果某个中间件没有调用_next(context),那么就不会调用后续的中间件,所以中间件启用的顺序是需要特别考虑的。
以上代码中三个中间件输出到控制台的信息顺序如下:
(1)
(2)
(3)
【3】
【2】
【1】
个人认为,“前道工序”应重点处理Request,“后道工序”应重点处理Response。
三、做一个类似MVC的中间件
我们做一个中间件,让其返回一些内容给客户端:
public class MyMvcMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; public MyMvcMiddleware(RequestDelegate next, ILogger<DemoAMiddleware> logger) { _next = next; _logger = logger; } public async Task Invoke(HttpContext context) { var str = "hello,world!"; await context.Response.WriteAsync(str); } }
这个中间件只是返回固定的字符串,我们还可以调用某个Controller的提供的方法。
public class MyMvcMiddleware { private readonly RequestDelegate _next; private readonly ILogger _logger; public MyMvcMiddleware(RequestDelegate next, ILogger<DemoAMiddleware> logger) { _next = next; _logger = logger; } public async Task Invoke(HttpContext context) {
var obj = context.RequestServices.GetRequiredService<ArticleController>().GetArticleList(); var str = JsonConvert.SerializeObject(obj); await context.Response.WriteAsync(str); } }
ArticleController的定义如下:
public class ArticleController : Controller { private readonly SalesContext _context; private readonly ILogger _logger; private readonly IMemoryCache _cache; public ArticleController(SalesContext context, ILogger<ArticleController> logger, IMemoryCache memoryCache) { _context = context; _logger = logger; _cache = memoryCache; } [HttpGet] public ResultObject GetArticleList() { _logger.LogInformation("==GetArticleList=="); List<Article> articles = _context.Articles .AsNoTracking() .ToList(); return new ResultObject { result = articles }; } }
要在中间件中通过依赖使用该Controller,需要将其注册到DI容器:
public void ConfigureServices(IServiceCollection services) { services.AddScoped<ArticleController>(); }
以上中间件实现了一个文章信息查询的功能,如果在此中间件内先判断路径,再根据不同的路径调用不同的Contorller提供的服务,就可以形成一个简单的MVC中间件了。
四、中间件的用途
中间件的使用体现了AOP(面向切片)的编程思想,在不修改现有代码的情况下,通过增加一些中间件实现一些特定逻辑,可以做的事情很多,比如:
URL重写
缓存处理
异常处理
用户认证
五、中间件的注册顺序
前文提到中间件的注册顺序是比较重要的,建议的顺序如下:
1. 异常/错误处理
2. 静态文件服务器
3. 身份验证
4. MVC
推荐阅读
-
循序渐进学.Net Core Web Api开发系列【0】:序言与目录
-
循序渐进学.Net Core Web Api开发系列【1】:开发环境
-
循序渐进学.Net Core Web Api开发系列【2】:利用Swagger调试WebApi
-
循序渐进学.Net Core Web Api开发系列【6】:配置文件appsettings.json
-
循序渐进学.Net Core Web Api开发系列【10】:使用日志
-
循序渐进学.Net Core Web Api开发系列【15】:应用安全
-
循序渐进学.Net Core Web Api开发系列【14】:异常处理
-
循序渐进学.Net Core Web Api开发系列【11】:依赖注入
-
循序渐进学.Net Core Web Api开发系列【12】:缓存
-
循序渐进学.Net Core Web Api开发系列【8】:访问数据库(基本功能)