.NET Core開發日誌——RequestDelegate

来源:https://www.cnblogs.com/kenwoo/archive/2018/08/02/9404671.html
-Advertisement-
Play Games

本文主要是對 ".NET Core開發日誌——Middleware" 的補遺,但是會從看起來平平無奇的RequestDelegate開始敘述,所以以其作為標題,也是合情合理。 RequestDelegate是一種委托類型,其全貌為 ,MSDN上對它的解釋,"A function that can p ...


本文主要是對.NET Core開發日誌——Middleware的補遺,但是會從看起來平平無奇的RequestDelegate開始敘述,所以以其作為標題,也是合情合理。

RequestDelegate是一種委托類型,其全貌為public delegate Task RequestDelegate(HttpContext context),MSDN上對它的解釋,"A function that can process an HTTP request."——處理HTTP請求的函數。唯一參數,是最熟悉不過的HttpContext,返回值則是表示請求處理完成的非同步操作類型。

可以將其理解為ASP.NET Core中對一切HTTP請求處理的抽象(委托類型本身可視為函數模板,其實現具有統一的參數列表及返回值類型),沒有它整個框架就失去了對HTTP請求的處理能力。

並且它也是構成Middleware的基石。或者更準確地說參數與返回值都是其的Func<RequestDelegate, RequestDelegate>委托類型正是維持Middleware運轉的核心齒輪。

組裝齒輪的地方位於ApplicationBuilder類之內,其中包含著所有齒輪的集合。

private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();

以及添加齒輪的方法:

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

在Startup類的Configure方法里調用以上ApplicationBuilder的Use方法,就可以完成一個最簡單的Middleware。

public void Configure(IApplicationBuilder app)
{
    app.Use(_ =>
    {
        return context =>
        {
            return context.Response.WriteAsync("Hello, World!");
        };
        
    });
}

齒輪要想變成Middleware,在完成添加後,還需要經過組裝。

public RequestDelegate Build()
{
    RequestDelegate app = context =>
    {
        context.Response.StatusCode = 404;
        return Task.CompletedTask;
    };

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

    return app;
}

Build方法里先定義了最底層的零件——app,context => { context.Response.StatusCode = 404; return Task.CompletedTask; },這段代碼意味著,如果沒有添加任何Middleware的話,ASP.NET Core站點啟動後,會直接出現404的錯誤。

接下的一段,遍歷倒序排列的齒輪,開始正式組裝。

在上述例子里,只使用了一個齒輪:

_ =>
{
    return context =>
    {
        return context.Response.WriteAsync("Hello, World!");
    };
    
}

那麼第一次也是最後一次迴圈後,執行component(app)操作,app被重新賦值為:

context => context.Response.WriteAsync("Hello, World!");

組裝的結果便是app的值。

這個組裝過程在WebHost進行BuildApplication時開始操作。從此方法的返回值類型可以看出,雖然明義上是創建Application,其實生成的是RequestDelegate。

private RequestDelegate BuildApplication()
{
    try
    {
        ...

        var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
        var builder = builderFactory.CreateBuilder(Server.Features);
        ...
        Action<IApplicationBuilder> configure = _startup.Configure;
        ...

        configure(builder);

        return builder.Build();
    }
    ...
}    

而這個RequestDelegate最終會在HostingApplication類的ProcessRequestAsync方法里被調用。

public virtual async Task StartAsync(CancellationToken cancellationToken = default)
{
    ...

    var application = BuildApplication();

    ...
    var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory);
    ...
}    

public HostingApplication(
    RequestDelegate application,
    ILogger logger,
    DiagnosticListener diagnosticSource,
    IHttpContextFactory httpContextFactory)
{
    _application = application;
    _diagnostics = new HostingApplicationDiagnostics(logger, diagnosticSource);
    _httpContextFactory = httpContextFactory;
}

public Task ProcessRequestAsync(Context context)
{
    return _application(context.HttpContext);
}

上例中的執行結果即是顯示Hello, World!字元。

404的錯誤不再出現,意味著這種Middleware只會完成自己對HTTP請求的處理,並不會將請求傳至下一層的Middleware。

要想達成不斷傳遞請求的目的,需要使用另一種Use擴展方法。

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);
        };
    });
}

在實際代碼中可以這麼寫:

public void Configure(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("I am a Middleware!\n");
        await next.Invoke();
    });

    app.Use(_ =>
    {
        return context =>
        {
            return context.Response.WriteAsync("Hello, World!");
        };
    });
}

現在多了個Middleware,繼續上面的組裝過程。app的值最終被賦值為:

async context =>
{
    Func<Task> simpleNext = () => context.Response.WriteAsync("Hello, World!"); 

    await context.Response.WriteAsync("I am a Middleware!\n");
    await simpleNext.Invoke();
};

顯示結果為:

I am a Middleware!
Hello, World!

下麵的流程圖中可以清楚地說明這個過程。

如果把await next.Invoke()註釋掉的話,

public void Configure(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("I am a Middleware!\n");
        //await next.Invoke();
    });

    app.Use(_ =>
    {
        return context =>
        {
            return context.Response.WriteAsync("Hello, World!");
        };

    });
}

上例中第一個Middleware處理完後,不會繼續交給第二個Middleware處理。註意以下simpleNext的方法只被定義而沒有被調用。

async context =>
{
    Func<Task> simpleNext = () => context.Response.WriteAsync("Hello, World!"); 

    await context.Response.WriteAsync("I am a Middleware!\n");
};

這種情況被稱為短路(short-circuiting)。

做短路處理的Middleware一般會放在所有Middleware的最後,以作為整個pipeline的終點。

並且更常見的方式是用Run擴展方法。

public static void Run(this IApplicationBuilder app, RequestDelegate handler)
{
    ...

    app.Use(_ => handler);
}

所以可以把上面例子的代碼改成下麵的形式:

public void Configure(IApplicationBuilder app)
{
    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("I am a Middleware!\n");
        await next.Invoke();
    });

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello, World!");
    });
}

除了短路之外,Middleware處理時還可以有分支的情況。

public void Configure(IApplicationBuilder app)
{
    app.Map("/branch1", ab => {
        ab.Run(async context =>
        {
            await context.Response.WriteAsync("Map branch 1");
        });
    });

    app.Map("/branch2", ab => {
        ab.Run(async context =>
        {
            await context.Response.WriteAsync("Map branch 2");
        });
    });

    app.Use(async (context, next) =>
    {
        await context.Response.WriteAsync("I am a Middleware!\n");
        await next.Invoke();
    });

    app.Run(async context =>
    {
        await context.Response.WriteAsync("Hello, World!");
    });
}

URL地址後面跟著branch1時:

URL地址後面跟著branch2時:

其它情況下:

Map擴展方法的代碼實現:

public static IApplicationBuilder Map(this IApplicationBuilder app, PathString pathMatch, Action<IApplicationBuilder> configuration)
{
    ...

    // create branch
    var branchBuilder = app.New();
    configuration(branchBuilder);
    var branch = branchBuilder.Build();

    var options = new MapOptions
    {
        Branch = branch,
        PathMatch = pathMatch,
    };
    return app.Use(next => new MapMiddleware(next, options).Invoke);
}

創建分支的辦法就是重新實例化一個ApplicationBuilder。

public IApplicationBuilder New()
{
    return new ApplicationBuilder(this);
}

對分支的處理則是封裝在MapMiddleware類之中。

public async Task Invoke(HttpContext context)
{
    ...

    PathString matchedPath;
    PathString remainingPath;

    if (context.Request.Path.StartsWithSegments(_options.PathMatch, out matchedPath, out remainingPath))
    {
        // Update the path
        var path = context.Request.Path;
        var pathBase = context.Request.PathBase;
        context.Request.PathBase = pathBase.Add(matchedPath);
        context.Request.Path = remainingPath;

        try
        {
            await _options.Branch(context);
        }
        finally
        {
            context.Request.PathBase = pathBase;
            context.Request.Path = path;
        }
    }
    else
    {
        await _next(context);
    }
}

說到MapMiddleware,不得不提及各種以Use開頭的擴展方法,比如UseStaticFiles,UseMvc,UsePathBase等等。

這些方法內部都會調用UseMiddleware方法以使用各類定製的Middleware類。如下麵UsePathBase的代碼:

public static IApplicationBuilder UsePathBase(this IApplicationBuilder app, PathString pathBase)
{
    ...

    // Strip trailing slashes
    pathBase = pathBase.Value?.TrimEnd('/');
    if (!pathBase.HasValue)
    {
        return app;
    }

    return app.UseMiddleware<UsePathBaseMiddleware>(pathBase);
}

而從UseMiddleware方法中可以獲知,Middleware類需滿足兩者條件之一才能被有效使用。其一是實現IMiddleware,其二,必須有Invoke或者InvokeAsync方法,且方法至少要有一個HttpContext類型參數(它還只能是放第一個),同時返回值需要是Task類型。

internal const string InvokeMethodName = "Invoke";
internal const string InvokeAsyncMethodName = "InvokeAsync";

public static IApplicationBuilder UseMiddleware(this IApplicationBuilder app, Type middleware, params object[] args)
{
    if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo()))
    {
        ...

        return UseMiddlewareInterface(app, middleware);
    }

    var applicationServices = app.ApplicationServices;
    return app.Use(next =>
    {
        var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
        var invokeMethods = methods.Where(m =>
            string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)
            || string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)
            ).ToArray();

        ...

        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;
            ...
            return factory(instance, context, serviceProvider);
        };
    });
}

對ASP.NET Core中Middleware的介紹到此終於可以告一段落,希望這兩篇文章能夠為讀者提供些許助力。


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

-Advertisement-
Play Games
更多相關文章
  • 這次遇到一個需求,就是將整個界面列印在A4紙上。 需求清楚後,Bing一下關於列印,就找打一個類PrintDialog ,其中兩個方法可能會用到: 特別是public void PrintVisual(Visual visual, string description)可以直接傳一個控制項就能列印出來 ...
  • ZedGraph設置輔助線 1.一般來說ZedGraph設置參考線可以用 ZedGraph對象.YAxis.MajorGrid.IsVisible = True '水平參考線 ZedGraph對象.XAxis.MajorGrid.IsVisible = True '垂直參考線 2.就是通過在ZedG ...
  • Jquery AJAX POST與GET之間的區別 Jquery AJAX POST與GET之間的區別 GET 就是一個相同的URL只有一個結果,瀏覽器直接就可以拿出來進行獲取,比如抓取介面get方式的內容,或者說直接獲取網站源碼,可以使用get進行抓取,所以說get主要是用來獲取/抓取。 Ajax ...
  • 1. Swagger是什麼? Swagger 是一個規範和完整的框架,用於生成、描述、調用和可視化 RESTful 風格的 Web 服務。總體目標是使客戶端和文件系統作為伺服器以同樣的速度來更新。文件的方法,參數和模型緊密集成到伺服器端的代碼,允許API來始終保持同步。Swagger 讓部署管理和使 ...
  • 0.簡介 事件匯流排就是訂閱/發佈模式的一種實現,本質上事件匯流排的存在是為了降低耦合而存在的。 從上圖可以看到事件由發佈者發佈到事件匯流排處理器當中,然後經由事件匯流排處理器調用訂閱者的處理方法,而發佈者和訂閱者之間並沒有耦合關係。 像 Windows 本身的設計也是基於事件驅動,當用戶點擊了某個按鈕,那 ...
  • 在我們平時項目中經常會遇到定時任務,比如定時同步數據,定時備份數據,定時統計數據等,定時任務我們都知道使用Quartz.net,此系列寫的也是Quartz,但是在此之前,我們先用其他方式做個簡單的定時任務進行入門。 首先呢,我們現在自己先寫一個簡單的定時迴圈任務,話不多說,直接上代碼: 第一步:創建 ...
  • 專為解答C#初級問題 QQ 群 731738614 ...
  • 本文是為了學習ABP的使用,是翻譯ABP官方文檔的一篇實戰教程,我暫時是優先翻譯自己感興趣或者比較想學習的部分,後續有時間希望能將ABP系列翻譯出來,除了自己能學習外,有可能的話希望幫助一些英文閱讀能力稍微差一點的同學(當然我自己也不一定翻譯的多好,大家共同學習)。 其實這篇文章也花了我一些時間,突 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...