【ASP.NET Core】運行原理[2]:啟動WebHost

来源:http://www.cnblogs.com/neverc/archive/2017/12/12/8029419.html
-Advertisement-
Play Games

本節將分析 代碼,確定是如何一步一步到我們註冊的中間件,並介紹幾種Configure的方式。 源代碼參考.NET Core 2.0.0 "WebHost" "Kestrel" "HttpAbstractions" 目錄 Server.StartAsync Server IHttpApplicatio ...


本節將分析WebHost.StartAsync();代碼,確定是如何一步一步到我們註冊的中間件,並介紹幾種Configure的方式。

源代碼參考.NET Core 2.0.0

目錄

  • Server.StartAsync
    • Server
    • IHttpApplication
    • HttpContextFactory
    • HttpContext
  • Configure
    • IApplicationBuilder
    • Use
    • Run
    • UseMiddleware
    • UseWhen
    • MapWhen
    • Map

Server.StartAsync

在上節我們知道WebHost.StartAsync內部是調用Server.StartAsync的。

public async Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
{
    async Task OnBind(ListenOptions endpoint)
    {
        var connectionHandler = new ConnectionHandler<TContext>(endpoint, ServiceContext, application);
        var transport = _transportFactory.Create(endpoint, connectionHandler);
        _transports.Add(transport);

        await transport.BindAsync().ConfigureAwait(false);
    }

    await AddressBinder.BindAsync(_serverAddresses, Options.ListenOptions, Trace, OnBind).ConfigureAwait(false);
}

參數application即為之前的new HostingApplication。在這裡說下大概的流程:

KestrelServer.StartAsync -> new ConnectionHandler<TContext>().OnConnection -> new FrameConnection().StartRequestProcessing() -> 
new Frame<TContext>().ProcessRequestsAsync() -> _application.CreateContext(this) && _application.ProcessRequestAsync(context)

如果你需要更細節的流程,可參考如下:

LibuvTransportFactory -> LibuvTransport.BindAsync() -> ListenerPrimary.StartAsync() -> 
listener.ListenSocket.Listen(LibuvConstants.ListenBacklog, ConnectionCallback, listener) -> listener.OnConnection(stream, status) -> ConnectionCallback() ->
new LibuvConnection(this, socket).Start() -> ConnectionHandler.OnConnection() -> connection.StartRequestProcessing() -> 
ProcessRequestsAsync -> CreateFrame -> await _frame.ProcessRequestsAsync()
  1. _application 為上面的HostingApplication;
  2. 每個WebHost.StartAsync 將創建唯一的一個HostingApplication實例併在每次請求時使用。
  3. 由Frame類調用HostingApplication的方法。

下麵展示Frame以及HostingApplication:

Frame

public class Frame<TContext> : Frame
{
    public override async Task ProcessRequestsAsync()
    {
        while (!_requestProcessingStopping)
        {
            Reset();

            EnsureHostHeaderExists();

            var messageBody = MessageBody.For(_httpVersion, FrameRequestHeaders, this);
            InitializeStreams(messageBody);

            var context = _application.CreateContext(this);
            try
            {
                await _application.ProcessRequestAsync(context);
            }
            finally
            {
                _application.DisposeContext(context, _applicationException);
            }
        }
    }
}

HostingApplication

public class HostingApplication : IHttpApplication<HostingApplication.Context>
{
    private readonly RequestDelegate _application;
    private readonly IHttpContextFactory _httpContextFactory;

    public HostingApplication(
        RequestDelegate application,
        IHttpContextFactory httpContextFactory)
    {
        _application = application;
        _httpContextFactory = httpContextFactory;
    }

    // Set up the request
    public Context CreateContext(IFeatureCollection contextFeatures)
    {
        var context = new Context();
        var httpContext = _httpContextFactory.Create(contextFeatures);
        context.HttpContext = httpContext;
        return context;
    }

    // Execute the request
    public Task ProcessRequestAsync(Context context)
    {
        return _application(context.HttpContext);
    }

    // Clean up the request
    public void DisposeContext(Context context, Exception exception)
    {
        var httpContext = context.HttpContext;
        _httpContextFactory.Dispose(httpContext);
    }

    public struct Context
    {
        public HttpContext HttpContext { get; set; }
    }
}

由此我們發現HttpContext是由HttpContextFactory創建的,其中_httpContextFactory則是上節在WebHostBuilder的BuildCommon註入的
同時在HostingApplication的ProcessRequestAsync方法中,我們看到我們的_application(Startup註冊的中間件)被調用了。
IHttpContextFactory

HttpContextFactory

public HttpContext Create(IFeatureCollection featureCollection)
{
    var httpContext = new DefaultHttpContext(featureCollection);
    if (_httpContextAccessor != null)
        _httpContextAccessor.HttpContext = httpContext;
    return httpContext;
}

而創建的HttpContext則是DefaultHttpContext類型:

public class DefaultHttpContext : HttpContext
{
    public virtual void Initialize(IFeatureCollection features)
    {
        _features = new FeatureReferences<FeatureInterfaces>(features);
        _request = InitializeHttpRequest();
        _response = InitializeHttpResponse();
    }

    public override HttpRequest Request => _request;

    public override HttpResponse Response => _response;
}

Configure

IApplicationBuilder

我們知道在Startup的Configure方法中,通過IApplicationBuilder可以註冊中間件。

public interface IApplicationBuilder
{
    IServiceProvider ApplicationServices { get; set; }
    RequestDelegate Build();
    IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
}

預設實現類為:

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

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

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

        return app;
    }
}

其中Use方法為註冊中間件。中間件的本質就是一個Func<RequestDelegate, RequestDelegate>對象。
該對象的傳入參數為下一個中間件,返回對象為本中間件。

而Build方法為生成一個RequestDelegate,在HostingApplication構造函數中的參數即為該對象。
在Build方法中,我們看到最後一個中間件為404中間件。其他的中間件都是通過Use方法註冊到內部維護的_components對象上。

Use

我們通過一個Use示例,來看下中間件的流程:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.Use(next => async context =>
    {
        Console.WriteLine("A begin");
        await next(context);
        Console.WriteLine("A end");
    });

    app.Use(next => async context =>
    {
        Console.WriteLine("B begin");
        await next(context);
        Console.WriteLine("B end");
    });
}

訪問結果:
A begin
B begin
B end
A end

流程圖:
流程圖

Run

當我們不使用next 下一個中間件的時候,我們可以使用Run方法來實現
Run方法接受一個RequestDelegate對象,本身是IApplicationBuilder的擴展方法。

public static void Run(this IApplicationBuilder app, RequestDelegate handler);
{
    app.Use(_ => handler);
}

Run示例

app.Run(context=>context.Response.WriteAsync("Run Core"));

該示例相當於:

app.Use(next => context => context.Response.WriteAsync("Run Core"));

UseMiddleware

而通常我們添加中間件的方式是通過UseMiddleware來更加方便的操作。

先看下IMiddleware:

public interface IMiddleware
{
    Task InvokeAsync(HttpContext context, RequestDelegate next);
}

參數next即為下一個中間件。

有2種實現UseMiddleware的方式:

  1. 實現IMiddleware介面。
  2. 基於介面約定的方法。

IMiddleware介面

public class DemoMiddle : IMiddleware
{
    public Task InvokeAsync(HttpContext context, RequestDelegate next)
    {
        return context.Response.WriteAsync("hello middleware");
    }
}

在使用IMiddleware介面的時候,還需要註冊該類到DI系統中。

約定

public class DemoMiddle
{
    private RequestDelegate _next;
    public DemoMiddle(RequestDelegate next)
    {
        _next = next;
    }
    public Task InvokeAsync(HttpContext context)
    {
        return context.Response.WriteAsync("hello middleware");
    }
}

這種方式,不用再註冊到DI中,如果需要對該類構造函數傳入參數,直接在app.UseMiddleware<DemoMiddle>("hi1");傳入參數即可。

UseWhen

app.Use(next => async context => { await context.Response.WriteAsync("Begin"); await next(context); });

app.UseWhen(context => context.Request.Path.Value == "/hello", branch => branch.Use(
    next => async context => { await context.Response.WriteAsync("hello"); await next(context); }));

app.Run(context => context.Response.WriteAsync("End"));

當我們訪問/hello時,結果為:BeginhelloEnd
分析源碼得知在構建管道的時候,克隆一個另外的IApplicationBuilder。

public static IApplicationBuilder UseWhen(this IApplicationBuilder app, Predicate predicate, Action<IApplicationBuilder> configuration)
{
    var branchBuilder = app.New();
    configuration(branchBuilder);

    return app.Use(main =>
    {
        // This is called only when the main application builder
        // is built, not per request.
        branchBuilder.Run(main);// 添加(調用)原來的中間件
        var branch = branchBuilder.Build();

        return context => predicate(context) ? branch(context): main(context);
    });
}

MapWhen

app.Use(next => async context => { await context.Response.WriteAsync("Begin"); await next(context); });

app.MapWhen(context => context.Request.Path.Value == "/hello", app2 => app2.Run(context => context.Response.WriteAsync("hello")));

app.Run(context => context.Response.WriteAsync("End"));

當我們訪問/hello時,結果為:Beginhello
分析源碼得知在構建管道的時候,新分支並沒有再調用原來的中間件。

public static IApplicationBuilder MapWhen(this IApplicationBuilder app, Predicate predicate, Action<IApplicationBuilder> configuration)
{
    var branchBuilder = app.New();
    configuration(branchBuilder);
    var branch = branchBuilder.Build();
    return app.Use(next => context => predicate(context) ? branch(context): next(context));
}

Map

app.Map("/hello", app2 => app2.Run(context => context.Response.WriteAsync("hello")));

當我們訪問/hello時,結果為:Beginhello。與MapWhen效果一樣。
如果我們只是判斷URLPath的話,通常我們會使用Map方法。

以上是常用的註冊中間件的方式。

本文鏈接:http://neverc.cnblogs.com/p/8029419.html


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

-Advertisement-
Play Games
更多相關文章
  • NPOI 官網下載DLL:http://npoi.codeplex.com/releases 1、讀取Excel轉為DataTable ...
  • 介面是C#的一種引用數據類型。介面像是一個抽象類,可以定義方法成員,屬性,索引器和事件等,但是介面不提供對成員的實現,繼承介面的類必須提供介面成員的實現。 類用於描述的是事物的共性基本功能,介面用於定義的都是事物的額外功能。 一 介面的好處 規範性:定義介面像是在定義一種規範,當一個項目龐大複雜的時 ...
  • C# 語言經過專門設計,以便不同庫中的基類與派生類之間的版本控制可以不斷向前發展,同時保持後向相容。 這具有多方面的意義。例如,這意味著在基類中引入與派生類中的某個成員具有相同名稱的新成員在 C# 中是完全支持的,不會導致意外行為。 它還意味著類必須顯式聲明某方法是要替代一個繼承方法,還是本身就是一 ...
  • Nginx在集群上使用Redis資料庫進行身份驗證,達到了支持集群、分散式。在此基礎上能夠實現單點登錄、時效性的訪問,結合WebApi最大限度地發揮了後臺身份驗證的管理Nginx集群使用Redis資料庫,客戶端利用 http basic身份驗證,訪問WebApi獲得Token並將Token存儲到Re... ...
  • 項目中遇到C#調用C++演算法庫的情況,C++內部運算結果返回矩形坐標數組(事先長度未知且不可預計),下麵方法適用於訪問C++內部分配的任何結構體類型數組。當時想當然的用ref array[]傳遞參數,能計算能分配,但是在C#里只得到arr長度是1,無法訪問後續數組Item。 C++ 介面示例: 結構 ...
  • 動軟代碼生成器 官網www.maticsoft.com幫助網站http://www.maticsoft.com/help/default.htm# 動軟代碼生成器 官網www.maticsoft.com幫助網站http://www.maticsoft.com/help/default.htm# 動軟 ...
  • 演示產品源碼下載地址:http://www.jinhusns.com ...
  • 來源:http://blog.csdn.net/xinglun88/article/details/19987719 第一步:下載並安裝PDMReader,資源網站: http://www.pdmreader.com/ 第二步:打開PDMReader,新建項目:test; 第三步:在項目test點擊 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...