asp.net core mvc 管道之中間件

来源:https://www.cnblogs.com/xxred/archive/2018/09/03/9576622.html
-Advertisement-
Play Games

asp.net core mvc 管道之中間件 http請求處理管道通過註冊中間件來實現各種功能,松耦合併且很靈活 此文簡單介紹asp.net core mvc中間件的註冊以及運行過程 通過理解中間件,將asp.net core mvc分解,以便更好地學習 中間件寫法 先看一個簡單的中間件,next ...


asp.net core mvc 管道之中間件

  • http請求處理管道通過註冊中間件來實現各種功能,松耦合併且很靈活
  • 此文簡單介紹asp.net core mvc中間件的註冊以及運行過程
  • 通過理解中間件,將asp.net core mvc分解,以便更好地學習

中間件寫法

  • 先看一個簡單的中間件,next是下一個委托方法,在本中間件的Invoke方法裡面需要執行它,否則處理就會終止,消息處理到此中間件就會返回了
  • 因此,根據這個約定,一個中間生成一個委托方法,需要把所有的委托方法處理成嵌套的委托,即每個中間件裡面執行下一個委托,這樣處理過程就像管道一樣連接起來,每個中間件就是管道處理的節點
  • 至於為什麼要這樣寫中間件,這是約定好的,還有註意點,下麵將會講到
    public class Middleware
    {
        private readonly RequestDelegate _next;

        public RouterMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext httpContext)
        {
            // do something
            await _next.Invoke(httpContext);
            // do something
        }
    }

中間件管道生成

  • 以上中間件會通過方法生成一個委托,並添加到委托集合,中間生成委托的過程後面講
namespace Microsoft.AspNetCore.Builder.Internal
{
    public class ApplicationBuilder : IApplicationBuilder
    {
        private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();
        public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware)
        {
            _components.Add(middleware);
            return this;
        }
    }
}
  • 最後的ApplicationBuilder.Build方法會處理所有註冊的中間件生成的委托集合,將所有中間件生成的委托集合,處理成嵌套的形式,最終得到一個委托,連成一段管道。
  • 以下方法首先聲明一個響應404的委托方法,把它當成所有中間件的最後一個,當然它不一定會被執行到,因為某個中間件可能不會調用它
  • 然後將這個委托作為參數,傳入並執行_components這個委托集合裡面的每一個委托,_components就是所有註冊的中間件生成的委托集合,Reverse方法將集合反轉,從最後註冊的中間件對應的委托開始處理
  • 所以呢中間件的註冊是有順序的,也就是Startup.cs類裡面的Configure方法,裡面的每個Use開頭的方法都對應一個中間件註冊,代碼的順序就是註冊的順序,也是執行的順序,千萬不能寫錯了。因為MVC處於處理流程的最後面,因此UseMvc方法總是位於最後
  • 在看component,是從_components委托集合裡面取出來的,執行後又得到一個RequestDelegate類型的委托,因此由中間件生成的委托的類型應該是Func<RequestDelegate, RequestDelegate>
        public RequestDelegate Build()
        {
            RequestDelegate app = context =>
            {
                context.Response.StatusCode = 404;
                return Task.CompletedTask;
            };

            foreach (var component in _components.Reverse())
            {
                app = component(app);
            }

            return app;
        }

中間件生成委托

  • 以下是中間件註冊方法,實際是調用ApplicationBuilder.Use方法,將中間件生成的委托加入委托集合,完成中間件註冊
  • app.Use方法參數,就是上面需要的類型Func<RequestDelegate, RequestDelegate>的委托,該委托的參數next就是下一個中間件對應的委托,返回值就是中間件的Invoke方法對應的委托,該方法用到了next
  • 源碼位於Microsoft.AspNetCore.Builder.UseMiddlewareExtensions這個類
        public static IApplicationBuilder UseMiddleware<TMiddleware>(this IApplicationBuilder app, params object[] args)
        {
            return app.UseMiddleware(typeof(TMiddleware), args);
        }

        public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
        {

            // 省略部分代碼

            var applicationServices = app.ApplicationServices;
            return app.Use(next =>
            {             

                // 省略部分代碼

                var ctorArgs = new object[args.Length + 1];
                ctorArgs[0] = next;
                Array.Copy(args, 0, ctorArgs, 1, args.Length);
                var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
                if (parameters.Length == 1)
                {
                    return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
                }

                var factory = Compile<object>(methodinfo, parameters);

                return context =>
                {
                    var serviceProvider = context.RequestServices ?? applicationServices;
                    if (serviceProvider == null)
                    {
                        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
                    }

                    return factory(instance, context, serviceProvider);
                };
            });
        }

中間件寫法約定

  • 看以上代碼,第一種寫法,首先如果中間繼承自IMiddleware介面,則調用UseMiddlewareInterface方法。使用了介面規範,那麼你也不能亂寫了,只需要註意在Invoke方法調用next即可
        private static IApplicationBuilder UseMiddlewareInterface(IApplicationBuilder app, Type middlewareType)
        {
            return app.Use(next =>
            {
                return async context =>
                {
                    var middlewareFactory = (IMiddlewareFactory)context.RequestServices.GetService(typeof(IMiddlewareFactory));
                    if (middlewareFactory == null)
                    {
                        // No middleware factory
                        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoMiddlewareFactory(typeof(IMiddlewareFactory)));
                    }

                    var middleware = middlewareFactory.Create(middlewareType);
                    if (middleware == null)
                    {
                        // The factory returned null, it's a broken implementation
                        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(middlewareFactory.GetType(), middlewareType));
                    }

                    try
                    {
                        await middleware.InvokeAsync(context, next);
                    }
                    finally
                    {
                        middlewareFactory.Release(middleware);
                    }
                };
            });
        }
  • 第二種是開頭舉的例子,不繼承自介面
    • 至少要有名為InvokeInvokeAsync的一個方法
      public static class UseMiddlewareExtensions
      {
          internal const string InvokeMethodName = "Invoke";
          internal const string InvokeAsyncMethodName = "InvokeAsync";
      }
    
      var invokeMethods = methods.Where(m =>
          string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)
          || string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)
          ).ToArray();
    • 類的方法只能有一個
                if (invokeMethods.Length > 1)
                {
                    throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
                }

                if (invokeMethods.Length == 0)
                {
                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName, middleware));
                }
  • 類的返回值是Task
                var methodinfo = invokeMethods[0];
                if (!typeof(Task).IsAssignableFrom(methodinfo.ReturnType))
                {
                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
                }
  • 方法的參數至少有一個,且第一個參數必須為是HttpContext
                var parameters = methodinfo.GetParameters();
                if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
                {
                    throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
                }
  • 方法的參數如果只有一個,則將UseMiddleware方法傳入的自定義參數args加上下一個委托next,得到新的參數數組,然後創建中間件實例,生成Invoke方法對應委托。此處註意,如果中間件的構造函數中有其它參數,但是未註冊到ApplicationServices的話,需要在UseMiddleware方法中傳入
                var ctorArgs = new object[args.Length + 1];
                ctorArgs[0] = next;
                Array.Copy(args, 0, ctorArgs, 1, args.Length);
                var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs);
                if (parameters.Length == 1)
                {
                    return (RequestDelegate)methodinfo.CreateDelegate(typeof(RequestDelegate), instance);
                }
  • 方法的參數如果多於一個,則調用Compile方法,生成一個委托,該委托從IServiceProvider中獲取需要的參數的實例,再調用Invoke方法,相比上面的情況,多了一步從IServiceProvider獲取實例,註入到Invoke而已。
  • Compile方法使用了Linq表達式樹,源碼位於Microsoft.AspNetCore.Builder.UseMiddlewareExtensions,此處不作講解,因為我也不太懂
                var factory = Compile<object>(methodinfo, parameters);

                return context =>
                {
                    var serviceProvider = context.RequestServices ?? applicationServices;
                    if (serviceProvider == null)
                    {
                        throw new InvalidOperationException(Resources.FormatException_UseMiddlewareIServiceProviderNotAvailable(nameof(IServiceProvider)));
                    }

                    return factory(instance, context, serviceProvider);
                };

總結

  • 以上就是通過調試和閱讀源碼分析得到的結果,寫出來之後閱讀可能有偏差,但這是為了方便大家理解,感覺這個順序介紹會好理解點,反正我是理解了,介紹順序對我影響不大
  • 通過動手記錄的過程,把之前調試閱讀的時候沒發現或者沒理解的點都找到弄明白了,整明白了中間件的註冊過程以及需要註意的書寫規範,收穫顯而易見,所以源碼才是最好的文檔,而且文檔未必有這麼詳細。通過記錄,可以把細節補全甚至弄明白,這一點至關重要,再次體會到其重要性
  • 另外,千萬不要在大晚上寫技術博文啊,總結之類的東西,切記

最後,文章可能有更新,請閱讀原文獲得更好的體驗哦 https://www.cnblogs.com/xxred/p/9576622.html


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

-Advertisement-
Play Games
更多相關文章
  • 一 、前言 本文設計思想採用明德揚至簡設計法。VGA是最常見的視頻顯示介面,時序也較為簡單。本文從利用顯示屏通過VGA方式顯示測試圖案及靜態圖片著手帶大家接觸圖像顯示應用,算是為後續VGA顯示攝像頭採集圖像以及HDMI高清數字顯示方式打個基礎。 二、VGA顯示原理 關於VGA的詳細解釋可查看參考文獻 ...
  • 類載入機制中的雙親委派模型是非常重要的,本文從源碼的角度對雙親委派模式進行瞭解析,源碼調用基本邏輯很簡單. ...
  • 1.MyBatis架構(簡單介紹MyBatis的流程) 接下來簡單介紹一下這張圖:首先明確我們的目的就是要創建sqlsession然後利用這個對象去執行sql 完成CRUD。創建sqlsession的前提就是用session工廠去創建,利用工廠創建需要原材料啊,所以最頂端的MyBatis配置文件就是 ...
  • 1、非同步消息 當一個消息發送時候,消息會被交給消息代理,消息代理可以確保消息被髮送到指定的目的地,同時解放發送者,使其能夠繼續進行其它業務。消息代理通常有ActiveMQ、RabbitMQ...,目的地通常有隊列和主題,隊列採用點對點的模型,主題採用發佈訂閱模型 點對點模型:消息隊列可以有多個接受者 ...
  • 1、多線程安全問題 2、等待喚醒機制 ...
  • 給定兩個以字元串形式表示的非負整數 num1 和 num2,返回 num1 和 num2 的乘積,它們的乘積也表示為字元串形式。 示例 1: 示例 2: 說明: 從題目要求來看,應該是讓我們實現一個比較省記憶體的大數乘法,先分享幾個我在discuss中發現的不太切合題意的解法: 這個可以說是個毫無技術 ...
  • 一、概念和基本註解 從JDK1.5開始,引入了源代碼中的註解這一機制。註解使得 Java 源代碼中不但可以包含功能性的實現代碼,還可以包含元數據。 那麼什麼是元數據呢?所謂元數據,就是描述數據的數據。比如說一張圖片,圖片內容是它的主體數據,那麼像圖片的創建時間、修改時間、創建者等等這些數據,就是這張 ...
  • 一: 問題提出 現如今大家寫的netcore程式大多部署在linux平臺上,而且服務程式裡面可能會做各種複雜的操作,涉及到多數據源(mysql,redis,kafka)。成功部署成後臺 進程之後,你以為這樣就萬事大吉了? 卻不知當你更新代碼時,暴力的kill掉這個進程導致你的業務出現數據不一致,業務 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...