ASP.NET Core 中的中間件

来源:https://www.cnblogs.com/dudd/archive/2018/09/18/9670028.html
-Advertisement-
Play Games

前言   由於是第一次寫博客,如果您看到此文章,希望大家抱著找錯誤、批判的心態來看。 sky! 何為中間件? 在 ASP.NET Framework 中應該都知道請求管道。可參考: "淺談 ASP.NET 的內部機制" 系列,個人感覺超詳細。 _題外話: 說到請求管道,就想以前還 ...


前言

  由於是第一次寫博客,如果您看到此文章,希望大家抱著找錯誤、批判的心態來看。 sky!

何為中間件?

在 ASP.NET Framework 中應該都知道請求管道。可參考:淺談 ASP.NET 的內部機制 系列,個人感覺超詳細。

題外話:
說到請求管道,就想以前還是超菜鳥時有次面試被問到這個問題,一臉懵逼只說了 Controller→Action→View。臉紅啊!!

ASP.NET Core 中的中間件就是.net framework 請求管道的實現。下圖演示了 Middlerware 的概念。 沿黑色箭頭執行。

每一個中間件(Middleware1、Middleware2...)都是一個委托,這一系列委托就組成了整個管道。

中間件的寫法

  1. 直接在Startup.cs類的Configure方法里寫

    app.Use(async (context, next) =>
    {
        logger.LogInformation("中間件開始...");
        await next.Invoke(); //執行下一個中間件
        logger.LogInformation("中間件完成...");
    });

    結合上圖:

    //logic對應logger.LogInformation("中間件開始...");

    next();對應await next.Invoke();

    //more logic對應logger.LogInformation("中間件完成...");

    其中//logic(即請求)是順序執行。即:Middleware1→Middleware2→...→Middlewaren

    //more logic(即響應)是倒序執行。即:Middlewaren→...→Middleware2→Middleware1

  2. 同 1,只是不用 Use 而是用 Run:

     app.Run(async context =>
     {
         await context.Response.WriteAsync("請求終止了,下一步將會執行已執行過的Middleware的 //more logic");
     });

    Run 會終止請求,即管道中最後一個中間件,後面詳細剖析!

  3. 下麵這種寫法應該是比較合理的,也是比較優雅的

    新建一個類如下(該類是有強制規範的,詳細見下文):

    public class RequestTestMiddleware
    {
        private readonly RequestDelegate _next;
        public RequestTestMiddleware(RequestDelegate next)
        {
            _next = next;
        }
        public async Task InvokeAsync(HttpContext context)
        {
            //中間件開始 logic
            await _next(context);//執行下一個中間件
            //中間件完成 more logic
        }
    }

    Startup.cs類的Configure方法里添加如下代碼,效果和 1 相同:

    app.UseMiddleware<RequestTestMiddleware>();
    //app.UseMiddleware<RequestTestMiddleware>(params object[] parameters);//參數說明見下麵

    不知發現了沒,上面的InvokeAsync方法不是用的列印日誌,而是用的註釋。
    因為我們沒有引用logger對象,瞭解過 ASP.NET Core 的肯定知道依賴註入,我們只需要把ILogger註入進來就行了,改造如下:

    public class RequestTestMiddleware
     {
         private readonly RequestDelegate _next;
         public RequestTestMiddleware(RequestDelegate next)
         {
             _next = next;
         }
         public async Task InvokeAsync(HttpContext context, ILogger<TestMiddleware> logger)
         {
             logger.LogInformation("中間件開始 logic");
             await _next(context);
             logger.LogInformation("中間件完成 more logic");
         }
     }
  4. 通過依賴註入方法添加中間件:
    新建類 TestMiddleware.cs 註意依賴註入的位置和 3 不同

    public class TestMiddleware : IMiddleware
     {
         private readonly ILogger _logger;
         public TestMiddleware(ILogger<TestMiddleware> logger)
         {
             _logger = logger;
         }
         public async Task InvokeAsync(HttpContext context, RequestDelegate next)
         {
             _logger.LogInformation("中間件開始");
             await next(context);
             _logger.LogInformation("中間件完成");
         }
     }

    Startup.cs類的ConfigureServices方法里添加如下代碼:

    services.AddTransient<TestMiddleware>();

    Startup.cs類的Configure方法里添加如下代碼:

    app.UseMiddleware<TestMiddleware>();
  5. 還有一種第三方容器激活中間件

源代碼分析(部分)

  1. RunUse的實現

    直接放出源代碼:

     public static void Run(this IApplicationBuilder app, RequestDelegate handler)
     {
         if (app == null)
         {
             throw new ArgumentNullException(nameof(app));
         }
         if (handler == null)
         {
             throw new ArgumentNullException(nameof(handler));
         }
         app.Use(_ => handler);
     }
     public static IApplicationBuilder Use(this IApplicationBuilder app, Func<HttpContext, Func<Task>, Task> middleware)
     {
         return app.Use(next =>
         {
             return context =>
             {
                 Func<Task> simpleNext = () => next(context);
                 return middleware(context, simpleNext);
             };
         });
     }

    2 個方法最終調用的都是app.Use(),我們看下代碼:

    public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
    {
        _components.Add(middleware);
        return this;
    }

    _componentsIList<Func<RequestDelegate, RequestDelegate>>類型,其實就是把我們的Middleware添加到 _components 中,繼續看代碼:

    public RequestDelegate Build()
    {
        RequestDelegate app = context =>
        {
            context.Response.StatusCode = 404;
            return Task.CompletedTask;
        };
        foreach (var component in _components.Reverse())
        {
            app = component(app);
        }
        return app;
    }

    該方法會在Program.csMain方法的 CreateWebHostBuilder(args).Build().Run();Run() 方法執行。

    此方法把我們所有的Middleware再次組裝成 1 個新的RequestDelegate,最終的順序將會是:

    Middleware1()
    {
        next()=>Middleware2()
                {
                    next()=>Middleware3()
                            {
                                next()=>最後的那個返回404的委托
                            }
                }
    }

    不知道寫清楚了沒( ╯□╰ ). 其中next()=>Middleware2()的意思為:next()就是 Middleware2()

  2. 繼承 IMiddleware 和沒繼承 IMiddleware(根據規範必須要有 InvokeAsync 或 Invoke 方法等)的區別:

    按功能實現方面來說是沒區別的,但按性能方面應該是繼承了 IMiddleware 的方式要好很多,因為沒繼承 IMiddleware 的方式會用到反射。(未測試,由於繼承 IMiddleware 還需要用依賴註入這裡只是猜測)

    代碼見:
    Microsoft.AspNetCore.Http.Abstractions\Extensions\UseMiddlewareExtensions.cs 的 UseMiddleware 方法。

  3. 未繼承 IMiddleware 時的約定,直接看代碼吧:

    //1.在middleware中必須存在public且有返回值的方法
    var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
    
    //2.必須有‘Invoke’或‘InvokeAsync’方法
    var invokeMethods = methods.Where(m =>
        string.Equals(m.Name, "Invoke", StringComparison.Ordinal)
        || string.Equals(m.Name, "InvokeAsync", StringComparison.Ordinal)
        ).ToArray();
    
    //3.‘Invoke’和‘InvokeAsync’只能有1個
    if (invokeMethods.Length > 1) {}
    
    //4.‘Invoke’和‘InvokeAsync’必須要存在
    if (invokeMethods.Length == 0) {}
    var methodInfo = invokeMethods[0];
    
    //5.返回結果類型必須為Task
    if (!typeof(Task).IsAssignableFrom(methodInfo.ReturnType)){}
  4. 中間件傳參
    直接上代碼:

    public class RequestTestMiddleware
    {
        private readonly RequestDelegate _next;
        private int _i;
        public RequestTestMiddleware(RequestDelegate next, int i)
        {
            _next = next;
            _i = i;
        }
        public async Task InvokeAsync(HttpContext context, ILogger<TestMiddleware> logger)
        {
            logger.LogInformation($"通過參數傳遞i值:{_i}");
            logger.LogInformation("中間件開始");
            await _next(context);
            logger.LogInformation("中間件完成");
        }
    }

    Startup.cs類的Configure方法里:

    //參數類型為: params object[] args
    app.UseMiddleware<RequestTestMiddleware>(1);

    具體實現方式同樣在 Microsoft.AspNetCore.Http.Abstractions\Extensions\UseMiddlewareExtensions.cs 的 UseMiddleware 方法中

高級用法 Map MapWhen

  1. Map

    app.Map("/map", _app =>
    {
        _app.Run(async context =>
        {
            await context.Response.WriteAsync("Test Map!");
        });
    });

    當訪問https://localhost:5001/map時將返回 Test Map!

    這裡說一下,代碼中並沒有 MapController....

  2. MapWhen

    app.MapWhen(context => context.Request.Query.ContainsKey("branch"), _app =>
    {
        _app.Run(async context =>
        {
            await context.Response.WriteAsync("Test Map!");
        });
    });

    看源代碼會發現,MapWhen 的第二個參數(委托)並不是上面Use()next(),而是存在MapOptionsBranch屬性中,也是RequestDelegate委托

其他說明

  1. Middleware 的執行的有順序的,在合適的 Middleware 返回請求可時管道更短,速度更快。
    比如 UseStaticFiles(),靜態資源不必走驗證、MVC 中間件,所以該方法在中間件的前面執行。
  2. 我們看到有很多內置的中間件的用法是*Use**,其實是加了個擴展:

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

總結

  第一次寫博客,最大的感觸就是,然後就是思維邏輯有點混亂,總想用最簡單的語言來表達,就是掌握不好。最後看起來還是太啰嗦了點。最後說明,以上很可能有錯誤的說法,希望大家以批判的角度來看,有任何問題可在留言區留言!Thanks!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 希臘字母 |字母名稱 |大寫 | 小寫 | 大寫latex| 小寫latex| |字母名稱 |大寫 | 小寫 | 大寫latex| 小寫latex| | : : |: : | : : | : : | : : | |alpha| A | $\alpha$ | | \alpha ||xi | $\Xi$ ...
  • 1、索引:索引就是數據表中數據和響應的存儲位置的列表,利用索引可以提高在表或視圖中的查找數據的速度 2、索引分類:聚集索引和非聚集索引 1、唯一索引(如果有主鍵,那麼主鍵就是唯一索引) 2、索引視圖 3、全文索引 4、xml索引等等 3、語法: 4、為什麼使用索引 索引是一個單獨的、存儲在磁碟上的數 ...
  • var list1 = new List<int> { 1, 3, 5, 7, 9, 11, 13, 15 }; var list2 = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // List1:1 3 5 7 9 11 13 15 Console. ...
  • 一、前言 1、本文主要內容 Visual Studio Code 開發環境配置 使用 ASP.NET Core 構建Web應用 ASP.NET Core Web 應用啟動類說明 ASP.NET Core Web 項目結構說明 2、本教程環境信息 3、前置知識 你可能需要的前置知識 VS Code + ...
  • C# GetHashCode、Equals函數和鍵值對集合的關係 說明 HashCode:Hash碼。特性:兩個值,相同的的值生成的Hash肯定相同,但是不同的值生成的Hash很大程式上會不同。作用:求Hash值效率比引用類型判斷是否相等的函數Equals更快,所以被用來輔助判斷鍵值對集合的鍵值是否 ...
  • EF中的FluentApi作用是通過配置領域類來覆蓋預設的約定。在EF中,我們通過DbModelBuilder類來使用FluentApi,它的功能比數據註釋屬性更強大。 使用FluentApi時,我們在context類的OnModelCreating()方法中重寫配置項,一個慄子: 我們可以把Flu ...
  • 作為一個優秀的開源調度框架,Quartz 具有以下特點: 另外,作為 Spring 預設的調度框架,Quartz 很容易與 Spring 集成實現靈活可配置的調度功能。 quartz調度核心元素: 我這裡簡單記錄使用過程及代碼: 1:首先引用Quartz組件 2:using Quartz;using ...
  • WPF總體來說還是比較方便的,其中變化最大的主要是Listview和Treeview控制項,而且TreeView似乎在WPF是一個備受指責的控制項,很多人說他不好用。我這個demo主要是在wpf中使用TreeView控制項實現圖片查看功能,簡單的Grid佈局、TreeView控制項添加圖標、TreeView ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...