1. 程式人生 > >循序漸進學.Net Core Web Api開發系列【13】:中介軟體(Middleware)

循序漸進學.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