系列目錄 循序漸進學.Net Core Web Api開發系列目錄 本系列涉及到的源碼下載地址:https://github.com/seabluescn/Blog_WebApi 一、概述 本篇介紹如何使用中間件(Middleware)。 二、初步演練 先寫幾個中間件 以上中間件前兩個沒有做什麼正經 ...
系列目錄
本系列涉及到的源碼下載地址:https://github.com/seabluescn/Blog_WebApi
一、概述
本篇介紹如何使用中間件(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