.NET Core開發日誌——Startup

来源:https://www.cnblogs.com/kenwoo/archive/2018/07/28/9381106.html
-Advertisement-
Play Games

一個典型的ASP.NET Core應用程式會包含Program與Startup兩個文件。Program類中有應用程式的入口方法Main,其中的處理邏輯通常是創建一個WebHostBuilder,再生成WebHost,最後啟動之。 而在創建WebHostBuilder時又會常常會指定一個Startup ...


一個典型的ASP.NET Core應用程式會包含Program與Startup兩個文件。Program類中有應用程式的入口方法Main,其中的處理邏輯通常是創建一個WebHostBuilder,再生成WebHost,最後啟動之。

而在創建WebHostBuilder時又會常常會指定一個Startup類型。

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseStartup<Startup>();

這個Startup類里究竟做了哪些事情呢?

查一下UseStartup方法的實現內容:

public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
{
    var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name;

    return hostBuilder
        .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName)
        .ConfigureServices(services =>
        {
            if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
            {
                services.AddSingleton(typeof(IStartup), startupType);
            }
            else
            {
                services.AddSingleton(typeof(IStartup), sp =>
                {
                    var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
                    return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));
                });
            }
        });
}

可以看出所指定的Startup類型會在DI容器中註冊為單例形式,註冊的處理過程被封裝成Action

同時Startup類型可以有兩種實現方式:

  • 自定義實現IStartup介面的類
  • 內部定義的ConventionBasedStartup類

實際使用的Startup類經常是這樣的:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        ...
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        ...
    }
}

所以明顯不是第一種方式,那麼其又是怎麼與第二種方式關聯起來的?

ConventionBasedStartup的構造方法中傳入了StartupMethods類型的參數,它的LoadMethod方法里曝露了更多的信息。

public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName)
{
    var configureMethod = FindConfigureDelegate(startupType, environmentName);

    var servicesMethod = FindConfigureServicesDelegate(startupType, environmentName);
    var configureContainerMethod = FindConfigureContainerDelegate(startupType, environmentName);

    object instance = null;
    if (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic))
    {
        instance = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType);
    }

    // The type of the TContainerBuilder. If there is no ConfigureContainer method we can just use object as it's not
    // going to be used for anything.
    var type = configureContainerMethod.MethodInfo != null ? configureContainerMethod.GetContainerType() : typeof(object);

    var builder = (ConfigureServicesDelegateBuilder) Activator.CreateInstance(
        typeof(ConfigureServicesDelegateBuilder<>).MakeGenericType(type),
        hostingServiceProvider,
        servicesMethod,
        configureContainerMethod,
        instance);

    return new StartupMethods(instance, configureMethod.Build(instance), builder.Build());
}

它的內部處理中會找尋三種方法,ConfigureServices, Configure與ConfigureContainer。

private static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName)
{
    var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true);
    return new ConfigureBuilder(configureMethod);
}

private static ConfigureContainerBuilder FindConfigureContainerDelegate(Type startupType, string environmentName)
{
    var configureMethod = FindMethod(startupType, "Configure{0}Container", environmentName, typeof(void), required: false);
    return new ConfigureContainerBuilder(configureMethod);
}

private static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName)
{
    var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false)
        ?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false);
    return new ConfigureServicesBuilder(servicesMethod);
}

ConfigureServices方法用於在容器中註冊各種所需使用的服務介面類型,Configure方法中可以使用各種middleware(中間件),對HTTP請求pipeline(管道)進行配置。
這二者與IStartup介面的方法基本一致,而ConfigureContainer方法則是提供了一種引入第三方DI容器的功能。

public interface IStartup
{
    IServiceProvider ConfigureServices(IServiceCollection services);

    void Configure(IApplicationBuilder app);
}

使用第二種Startup類型的實現方式時,對於Configure方法的參數也可以更加靈活,不僅可以傳入IApplicationBuilder類型,還可以有其它已註冊過的任意介面類型。

ConfigureBuilder類的Build方法對額外參數的處理方法簡單明瞭,是IApplicationBuilder類型的直接傳入參數實例,不是的則從DI容器中獲取實例:

public class ConfigureBuilder
{
    public Action<IApplicationBuilder> Build(object instance) => builder => Invoke(instance, builder);

    private void Invoke(object instance, IApplicationBuilder builder)
    {
        // Create a scope for Configure, this allows creating scoped dependencies
        // without the hassle of manually creating a scope.
        using (var scope = builder.ApplicationServices.CreateScope())
        {
            var serviceProvider = scope.ServiceProvider;
            var parameterInfos = MethodInfo.GetParameters();
            var parameters = new object[parameterInfos.Length];
            for (var index = 0; index < parameterInfos.Length; index++)
            {
                var parameterInfo = parameterInfos[index];
                if (parameterInfo.ParameterType == typeof(IApplicationBuilder))
                {
                    parameters[index] = builder;
                }
                else
                {
                    try
                    {
                        parameters[index] = serviceProvider.GetRequiredService(parameterInfo.ParameterType);
                    }
                    ...
                }
            }
            MethodInfo.Invoke(instance, parameters);
        }
    }
}

此外,在找尋各種方法的處理中可以看到環境變數的身影。所以用第二種方式可以依據不同的環境變數定義同類型但不同名稱的方法,這樣可以省去寫不少if...else...的處理。

UseStartup方法中還只是申明瞭需要註冊Startup類型,實際的調用是在WebHostBuilder類執行Build方法時發生的。

private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
{
    ...

    foreach (var configureServices in _configureServicesDelegates)
    {
        configureServices(_context, services);
    }

    return services;    
}

至於Startup類型中方法的調用時機,則需跟蹤到WebHost類中。

首先是它的Initialize方法會確保獲得Startup類的實例,並調用ConfigureServices方法註冊服務介面。

private void EnsureApplicationServices()
{
    if (_applicationServices == null)
    {
        EnsureStartup();
        _applicationServices = _startup.ConfigureServices(_applicationServiceCollection);
    }
}

private void EnsureStartup()
{
    if (_startup != null)
    {
        return;
    }

    _startup = _hostingServiceProvider.GetService<IStartup>();

    ...
}

然後WebHost實例被啟動時,它的BuildApplication方法會創建一個ApplicationBuilder實例,以其作為Configure方法參數,同時調用Configure方法,這時Configure方法中對那些middleware的處理方式(即Func<RequestDelegate, RequestDelegate>方法)會被加入到ApplicationBuilder之中。

private RequestDelegate BuildApplication()
{
    try
    {
        _applicationServicesException?.Throw();
        EnsureServer();

        var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();
        var builder = builderFactory.CreateBuilder(Server.Features);
        builder.ApplicationServices = _applicationServices;

        var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>();
        Action<IApplicationBuilder> configure = _startup.Configure;
        foreach (var filter in startupFilters.Reverse())
        {
            configure = filter.Configure(configure);
        }

        configure(builder);

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

ApplicationBuilder的Build方法將這些處理邏輯嵌套起來,最底層的是返回404未找到的處理邏輯。

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

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

    return app;
}

BuildApplication方法返回已嵌套的RequestDelegate委托方法,併在之後生成的HostingApplication實例中,將其傳入它的構造方法。

public virtual async Task StartAsync(CancellationToken cancellationToken = default)
{
    HostingEventSource.Log.HostStart();
    _logger = _applicationServices.GetRequiredService<ILogger<WebHost>>();
    _logger.Starting();

    var application = BuildApplication();

    _applicationLifetime = _applicationServices.GetRequiredService<IApplicationLifetime>() as ApplicationLifetime;
    _hostedServiceExecutor = _applicationServices.GetRequiredService<HostedServiceExecutor>();
    var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticListener>();
    var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>();
    var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory);
    await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false);

    ...
}

上述方法中HostingApplication實例最終被傳入KestrelServer的啟動方法中,這樣才能在其內部調用HostingApplication的ProcessRequestAsync方法,並開始層層調用諸多的RequestDelegate方法。

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

如果覺得使用Startup類還是有點麻煩的話,直接使用WebHostBuilder所提供的擴展方法也是同樣的效果。

public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((hostingContext, config) =>
        {
            HostingEnvironment = hostingContext.HostingEnvironment;
            Configuration = config.Build();
        })
        .ConfigureServices(services =>
        {
            services.AddMvc();
        })
        .Configure(app =>
        {
            if (HostingEnvironment.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
            }

            app.UseMvcWithDefaultRoute();
            app.UseStaticFiles();
        });

不過使用獨立的Startup類有著額外的好處,Startup類可以被包含在與Program類不用的的程式集中。然後通過WebHostOptions類中StartupAssembly屬性的設定及其它相關處理,完成UseStartup方法同樣的功能。

private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors)
{
    ...

    if (!string.IsNullOrEmpty(_options.StartupAssembly))
    {
        try
        {
            var startupType = StartupLoader.FindStartupType(_options.StartupAssembly, _hostingEnvironment.EnvironmentName);

            if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
            {
                services.AddSingleton(typeof(IStartup), startupType);
            }
            else
            {
                services.AddSingleton(typeof(IStartup), sp =>
                {
                    var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>();
                    var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName);
                    return new ConventionBasedStartup(methods);
                });
            }
        }
        ...
    }

    ...

    return services;
}

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

-Advertisement-
Play Games
更多相關文章
  • HashMap實現原理 HashMap的底層使用數組+鏈表/紅黑樹實現。 這表示HashMap是Node數組構成,其中Node類的實現如下,可以看出這其實就是個鏈表,鏈表的每個結點是一個映射。 HashMap的每個下標都存放了一條鏈表。 常量/變數定義 關於modCount的作用見 "這篇blog" ...
  • 本文為原創,轉載請註明出處:https://www.cnblogs.com/Tom-shushu/p/9383066.html 本篇內容主要介紹:通過Servlet,JSP,Bootstrap框架以及MySQL等知識實現一個簡單地對資料庫信息進行:增,刪,改,查,分頁的操作; <一>設計資料庫 這裡 ...
  • try:將有可能導致出現異常的語句放到try塊中,如果使用了try語句後,後面的程式必須至少要跟一個except或者finally,否則程式會報錯 except:捕獲try塊中可能出現的異常 finally:不管程式是否有無異常,都會最終執行該語句 for example as below: 結果如 ...
  • C++自定義String字元串類 實現了各種基本操作,包括重載+號實現String的拼接 findSubStr函數,也就是尋找目標串在String中的位置,用到了KMP字元串搜索演算法。 include include using namespace std; class String; class ...
  • Java編程中獲取鍵盤輸入實現方法及註意事項 1. 鍵盤輸入一個數組 package com.wen201807.sort; import java.util.Scanner; public class Main { public static void main(String[] args) { ...
  • 1>:首先在CMD命令行中輸入:fsutil resource setautoreset true c:\ 2>:然後在運行services.msc 3>:找到Windows Process Activation Service服務 啟動該服務,啟動類型:自動 4>:繼續找到World Wide W ...
  • 最近面試時很多面試官都問到了EF框架 好記性不如爛筆頭 趕緊記下來 code-first是EF框架中的一種,是使用實體類來進行資料庫表的映射,所以實體類中的欄位要規範(我認為) 比如: 如果有外鍵的話 一定要搞清楚一對多、多對一和多對多的關係 比如一個用戶對應一個用戶詳細信息可以寫成這樣: 用戶詳細 ...
  • 首先先介紹一下這個項目,該項目實現了文本寫入及讀取,日誌寫入指定文件夾或預設文件夾,日誌數量控制,單個日誌大小控制,通過約定的參數讓用戶可以用更少的代碼解決問題。 1.讀取文本文件方法 使用:JIYUWU.TXT.TXTHelper.ReadToString(“文件物理路徑”) 1 public s ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...