• <button id="eiyoe"><acronym id="eiyoe"></acronym></button>
    <em id="eiyoe"></em>

  • <rp id="eiyoe"><acronym id="eiyoe"><input id="eiyoe"></input></acronym></rp>
      查看: 162|回復: 0
      上一主題 下一主題

      理解ASP.NET Core 啟動類(Startup)

      79910

      主題

      0

      好友

      積分

      離線 發信

      跳轉到指定樓層
      樓主
      發表于 2021-09-17 10:50 | 只看該作者 | 倒序瀏覽
      目錄
      • 準備工作:一份ASP.NET Core Web API應用程序
      • Startup類
        • Startup構造函數
        • ConfigureServices
        • Configure
      • 省略Startup類
        • IStartupFilter
          • IHostingStartup
            • HostingStartup 程序集
            • HostingStartup 特性
            • 激活HostingStarup程序集
              • 1.使用環境變量(推薦)
              • 2.在程序中配置
          • 多環境配置
            • 環境配置方式
              • 基于環境的 Startup
                • 1.將IWebHostEnvironment注入 Startup 類
                • 2.Startup 方法約定
                • 3.Startup 類約定

            準備工作:一份ASP.NET Core Web API應用程序

            當我們來到一個陌生的環境,第一件事就是找到廁所在哪。

            當我們接觸一份新框架時,第一件事就是找到程序入口,即Main方法

            public class Program
            {
                public static void Main(string[] args)
                {
                    CreateHostBuilder(args).Build().Run();
                }
            
                public static IHostBuilder CreateHostBuilder(string[] args) =>
                    Host.CreateDefaultBuilder(args)
                        .ConfigureWebHostDefaults(webBuilder =>
                        {
                            webBuilder.UseStartup<Startup>();
                        });
            }

            代碼很簡單,典型的建造者模式:通過IHostBuilder創建一個通用主機(Generic Host),然后啟動它(至于什么是通用主機,咱們后續的文章會說到)。咱們不要一上來就去研究CreateDefaultBuilder、ConfigureWebHostDefaults這些方法的源代碼,應該去尋找能看的見、摸得著的,很明顯,只有Startup。

            Startup類

            Startup類承擔應用的啟動任務,所以按照約定,起名為Startup,不過你可以修改為任意類名(強烈建議類名為Startup)。

            默認的Startup結構很簡單,包含:

            • 構造函數
            • Configuration屬性
            • ConfigureServices方法
            • Configure方法
            public class Startup
            {
                public Startup(IConfiguration configuration)
                {
                    Configuration = configuration;
                }
            
                public IConfiguration Configuration { get; }
            
                // This method gets called by the runtime. Use this method to add services to the container.
                // 該方法由運行時調用,使用該方法向DI容器添加服務
                public void ConfigureServices(IServiceCollection services)
                {
                }
            
                // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
                // 該方法由運行時調用,使用該方法配置HTTP請求管道
                public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
                {
                }
            }

            Startup構造函數

            當使用通用主機(Generic Host)時,Startup構造函數支持注入以下三種服務類型:

            • IConfiguration
            • IWebHostEnvironment
            • IHostEnvironment
            public Startup(
                IConfiguration configuration,
                IHostEnvironment hostEnvironment,
                IWebHostEnvironment webHostEnvironment)
            {
                Configuration = configuration;
                HostEnvironment = hostEnvironment;
                WebHostEnvironment = webHostEnvironment;
            }
            
            public IConfiguration Configuration { get; }
            
            public IHostEnvironment HostEnvironment { get; set; }
            
            public IWebHostEnvironment WebHostEnvironment { get; set; }

            這里你會發現 HostEnvironmentWebHostEnvironment 的實例是同一個。別著急,后續文章我們聊到Host的時候,你就明白了。

            ConfigureServices

            • 該方法是可選的
            • 該方法用于添加服務到DI容器中
            • 該方法在Configure方法之前被調用
            • 該方法要么無參數,要么只能有一個參數且類型必須為IServiceCollection
            • 該方法內的代碼大多是形如Add{Service}的擴展方法

            常用的服務有(部分服務框架已默認注冊):

            • AddControllers:注冊Controller相關服務,內部調用了AddMvcCore、AddApiExplorer、AddAuthorization、AddCors、AddDataAnnotations、AddFormatterMappings等多個擴展方法
            • AddOptions:注冊Options相關服務,如IOptions<>、IOptionsSnapshot<>、IOptionsMonitor<>、IOptionsFactory<>、IOptionsMonitorCache<>等。很多服務都需要Options,所以很多服務注冊的擴展方法會在內部調用AddOptions
            • AddRouting:注冊路由相關服務,如IInlineConstraintResolver、LinkGenerator、IConfigureOptions<RouteOptions>、RoutePatternTransformer
            • AddAddLogging:注冊Logging相關服務,如ILoggerFactory、ILogger<>、IConfigureOptions<LoggerFilterOptions>>
            • AddAuthentication:注冊身份認證相關服務,以方便后續注冊JwtBearer、Cookie等服務
            • AddAuthorization:注冊用戶授權相關服務
            • AddMvc:注冊Mvc相關服務,比如Controllers、Views、RazorPages等
            • AddHealthChecks:注冊健康檢查相關服務,如HealthCheckService、IHostedService

            Configure

            • 該方法是必須的
            • 該方法用于配置HTTP請求管道,通過向管道添加中間件,應用不同的響應方式。
            • 該方法在ConfigureServices方法之后被調用
            • 該方法中的參數可以接受任何已注入到DI容器中的服務
            • 該方法內的代碼大多是形如Use{Middleware}的擴展方法
            • 該方法內中間件的注冊順序與代碼的書寫順序是一致的,先注冊的先執行,后注冊的后執行

            常用的中間件有

            • UseDeveloperExceptionPage:當發生異常時,展示開發人員異常信息頁。如圖

            • UseRouting:路由中間件,根據Url中的路徑導航到對應的Endpoint。必須與UseEndpoints搭配使用。
            • UseEndpoints:執行路由所選擇的Endpoint對應的委托。
            • UseAuthentication:身份認證中間件,用于對請求用戶的身份進行認證。比如,早晨上班打卡時,管理員認出你是公司員工,那么才允許你進入公司。
            • UseAuthorization:用戶授權中間件,用于對請求用戶進行授權。比如,雖然你是公司員工,但是你是一名.NET開發工程師,那么你只允許坐在.NET開發工程師區域的工位上,而不能坐在老總的辦公室里。
            • UseMvc:Mvc中間件。
            • UseHealthChecks:健康檢查中間件。
            • UseMiddleware:用來添加匿名中間件的,通過該方法,可以方便的添加自定義中間件。

            省略Startup類

            另外,Startup類也可以省略,直接進行如下配置即可(雖然可以這樣做,但是不推薦):

            public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        // ConfigureServices 可以調用多次,最終會將結果聚合
                        webBuilder.ConfigureServices(services =>
                        {
                        })
                        // Configure 如果調用多次,則只有最后一次生效
                        .Configure(app =>
                        {
                            var env = app.ApplicationServices.GetRequiredService<IWebHostEnvironment>();
                        });
                    });

            IStartupFilter

            public interface IStartupFilter
            {
                Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next);
            }

            有時,我們想要將一系列相關中間件的注冊封裝到一起,那么我們只需要通過實現IStartupFilter,并在Startup.ConfigureServices中配置IStartupFilter的依賴注入即可。

            • IStartupFilter中配置的中間件,總是比Startup類中Configure方法中的中間件先注冊;對于多個IStartupFilter實現,執行順序與服務注冊時的順序一致

            我們可以通過一個例子來驗證一下中間件的注冊順序。

            首先是三個IStartupFilter的實現類:

            public class FirstStartupFilter : IStartupFilter
            {
                public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
                => app =>
                {
                    app.Use((context, next) =>
                    {
                        Console.WriteLine("First");
                        return next();
                    });
                    next(app);
                };
            }
            
            public class SecondStartupFilter : IStartupFilter
            {
                public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
                => app =>
                {
                    app.Use((context, next) =>
                    {
                        Console.WriteLine("Second");
                        return next();
                    });
                    next(app);
                };
            }
            
            public class ThirdStartupFilter : IStartupFilter
            {
                public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
                => app =>
                {
                    app.Use((context, next) =>
                    {
                        Console.WriteLine("Third");
                        return next();
                    });
                    next(app);
                };
            }

            接下來進行注冊:

            public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                    .ConfigureServices(services =>
                    {
                        // 第一個被注冊
                        services.AddTransient<IStartupFilter, FirstStartupFilter>();
                    })
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseStartup<Startup>();
                    })
                    .ConfigureServices(services => 
                    {
                        // 第三個被注冊
                        services.AddTransient<IStartupFilter, ThirdStartupFilter>();
                    });
                    
            public class Startup
            {
                public void ConfigureServices(IServiceCollection services)
                {
                    // 第二個被注冊
                    services.AddTransient<IStartupFilter, SecondStartupFilter>();
                }
            
                public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
                {
                    // 第四個被注冊
                    app.Use((context, next) =>
                    {
                        Console.WriteLine("Forth");
                        return next();
                    });
                }
            }

            最后通過輸出可以看到,執行順序的確是這樣子的。

            First
            Second
            Third
            Forth

            IHostingStartup

            IStartupFilter不同的是,IHostingStartup可以在啟動時通過外部程序集向應用增加更多功能。不過這要求必須調用ConfigureWebHost、ConfigureWebHostDefaults等類似用來配置Web主機的擴展方法

            我們經常使用的Nuget包SkyApm.Agent.AspNetCore就使用了該特性。

            下面我們就來看一下該如何使用它。

            HostingStartup 程序集

            要創建HostingStartup程序集,可以通過創建類庫項目或無入口點的控制臺應用來實現。

            接下來咱們還是看一下上面提到過的SkyApm.Agent.AspNetCore

            using SkyApm.Agent.AspNetCore;
            
            [assembly: HostingStartup(typeof(SkyApmHostingStartup))]
            
            namespace SkyApm.Agent.AspNetCore
            {
                internal class SkyApmHostingStartup : IHostingStartup
                {
                    public void Configure(IWebHostBuilder builder)
                    {
                        builder.ConfigureServices(services => services.AddSkyAPM(ext => ext.AddAspNetCoreHosting()));
                    }
                }
            }

            該HostingStartup類:

            • 實現了IHostingStartup接口
            • Configure方法中使用IWebHostBuilder來添加增強功能
            • 配置了HostingStartup特性

            HostingStartup 特性

            HostingStartup特性用于標識哪個類是HostingStartup類,HostingStartup類需要實現IHostingStartup接口。

            當程序啟動時,會自動掃描入口程序集和配置的待激活的的程序集列表(參見下方:激活HostingStarup程序集),來找到所有的HostingStartup特性,并通過反射的方式創建Startup并調用Configure方法。

            SkyApm.Agent.AspNetCore為例

            using SkyApm.Agent.AspNetCore;
            
            [assembly: HostingStartup(typeof(SkyApmHostingStartup))]
            
            namespace SkyApm.Agent.AspNetCore
            {
                internal class SkyApmHostingStartup : IHostingStartup
                {
                    public void Configure(IWebHostBuilder builder)
                    {
                        builder.ConfigureServices(services => services.AddSkyAPM(ext => ext.AddAspNetCoreHosting()));
                    }
                }
            }

            激活HostingStarup程序集

            要激活HostingStarup程序集,我們有兩種配置方式:

            1.使用環境變量(推薦)

            使用環境變量,無需侵入程序代碼,所以我更推薦大家使用這種方式。

            配置環境變量ASPNETCORE_HOSTINGSTARTUPASSEMBLIES,多個程序集使用分號(;)進行分隔,用于添加要激活的程序集。變量WebHostDefaults.HostingStartupAssembliesKey就是指代這個環境變量的Key。

            另外,還有一個環境變量,叫做ASPNETCORE_HOSTINGSTARTUPEXCLUDEASSEMBLIES,多個程序集使用分號(;)進行分隔,用于排除要激活的程序集。變量WebHostDefaults.HostingStartupExcludeAssembliesKey就是指代這個環境變量的Key。

            我們在 launchSettings.json 中添加兩個程序集:

            "environmentVariables": {
                "ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "SkyAPM.Agent.AspNetCore;HostingStartupLibrary"
            }

            2.在程序中配置

            public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        webBuilder.UseSetting(
                            WebHostDefaults.HostingStartupAssembliesKey,
                            "SkyAPM.Agent.AspNetCore;HostingStartupLibrary")
                        .UseStartup<Startup>();
                    });

            這樣就配置完成了,很🐮🍺的一個功能點吧!

            需要注意的是,無論使用哪種配置方式,當存在多個HostingStartup程序集時,將按配置這些程序集時的書寫順序執行 Configure方法。

            多環境配置

            一款軟件,一般要經過需求分析、設計編碼,單元測試、集成測試以及系統測試等一系列測試流程,驗收,最終上線。那么,就至少需要4套環境來保證系統運行:

            • Development:開發環境,用于開發人員在本地對應用進行調試運行
            • Test:測試環境,用于測試人員對應用進行測試
            • Staging:預發布環境,用于在正式上線之前,對應用進行集成、測試和預覽,或用于驗收
            • Production:生產環境,應用的正式線上環境

            環境配置方式

            通過環境變量ASPNETCORE_ENVIRONMENT指定運行環境

            注意:如果未指定環境,默認情況下,為 Production

            在項目的Properties文件夾里面,有一個“launchSettings.json”文件,該文件是用于配置VS中項目啟動的。
            接下來我們就在launchSettings.json中配置一下。
            先解釋一下該文件中出現的幾個參數:

            • commandName:指定要啟動的Web服務器,有三個可選值:

            Project:啟動 Kestrel

            IISExpress:啟動IIS Express

            IIS:不啟用任何Web服務器,使用IIS

            • dotnetRunMessages:bool字符串,指示當使用 dotnet run 命令時,終端能夠及時響應并輸出消息,具體參考stackoverflow和github issue
            • launchBrowser:bool值,指示當程序啟動后,是否打開瀏覽器
            • launchUrl:默認啟動路徑
            • applicationUrl:應用程序Url列表,多個URL之間使用分號(;)進行分隔。當launchBrowser為true時,將{applicationUrl}/{launchUrl}作為瀏覽器默認訪問的Url
            • environmentVariables:環境變量集合,在該集合內配置環境變量
            {
              "$schema": "http://json.schemastore.org/launchsettings.json",
              "profiles": {
                // 如果不指定profile,則默認選擇第一個
                // Development
                "ASP.NET.WebAPI": {
                  "commandName": "Project",
                  "dotnetRunMessages": "true",
                  "launchBrowser": true,
                  "launchUrl": "weatherforecast",
                  "applicationUrl": "http://localhost:5000",
                  "environmentVariables": {
                    "ASPNETCORE_ENVIRONMENT": "Development"
                  }
                },
                // Test
                "ASP.NET.WebAPI.Test": {
                  "commandName": "Project",
                  "dotnetRunMessages": "true",
                  "launchBrowser": true,
                  "launchUrl": "weatherforecast",
                  "applicationUrl": "http://localhost:5000",
                  "environmentVariables": {
                    "ASPNETCORE_ENVIRONMENT": "Test"
                  }
                },
                // Staging
                "ASP.NET.WebAPI.Staging": {
                  "commandName": "Project",
                  "dotnetRunMessages": "true",
                  "launchBrowser": true,
                  "launchUrl": "weatherforecast",
                  "applicationUrl": "http://localhost:5000",
                  "environmentVariables": {
                    "ASPNETCORE_ENVIRONMENT": "Staging"
                  }
                },
                // Production
                "ASP.NET.WebAPI.Production": {
                  "commandName": "Project",
                  "dotnetRunMessages": "true",
                  "launchBrowser": true,
                  "launchUrl": "weatherforecast",
                  "applicationUrl": "http://localhost:5000",
                  "environmentVariables": {
                    "ASPNETCORE_ENVIRONMENT": "Production"
                  }
                },
                // 用于測試在未指定環境時,默認是否為Production
                "ASP.NET.WebAPI.Default": {
                  "commandName": "Project",
                  "dotnetRunMessages": "true",
                  "launchBrowser": true,
                  "launchUrl": "weatherforecast",
                  "applicationUrl": "http://localhost:5000"
                }
              }
            }

            配置完成后,就可以在VS上方工具欄中的項目啟動處選擇啟動項了

            基于環境的 Startup

            Startup類支持針對不同環境進行個性化配置,有三種方式:

            • 1.將IWebHostEnvironment注入 Startup 類
            • 2.Startup 方法約定
            • 3.Startup 類約定

            1.將IWebHostEnvironment注入 Startup 類

            通過將IWebHostEnvironment注入 Startup 類,然后在方法中使用條件判斷書寫不同環境下的代碼。該方式適用于多環境下,代碼差異較少的情況。

            public class Startup
            {
                public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
                {
                    Configuration = configuration;
                    WebHostEnvironment = webHostEnvironment;
                }
            
                public IConfiguration Configuration { get; }
            
                public IWebHostEnvironment WebHostEnvironment { get; }
            
                public void ConfigureServices(IServiceCollection services)
                {
                    if (WebHostEnvironment.IsDevelopment())
                    {
                        Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}");
                    }
                    else if (WebHostEnvironment.IsTest())
                    {
                        Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}");
                    }
                    else if (WebHostEnvironment.IsStaging())
                    {
                        Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}");
                    }
                    else if (WebHostEnvironment.IsProduction())
                    {
                        Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}");
                    }
                }
            
                public void Configure(IApplicationBuilder app)
                {
                    if (WebHostEnvironment.IsDevelopment())
                    {
                        Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}");
                    }
                    else if (WebHostEnvironment.IsTest())
                    {
                        Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}");
                    }
                    else if (WebHostEnvironment.IsStaging())
                    {
                        Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}");
                    }
                    else if (WebHostEnvironment.IsProduction())
                    {
                        Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}");
                    }
                }
            }
            
            public static class AppHostEnvironmentEnvExtensions
            {
                public static bool IsTest(this IHostEnvironment hostEnvironment)
                {
                    if (hostEnvironment == null)
                    {
                        throw new ArgumentNullException(nameof(hostEnvironment));
                    }
            
                    return hostEnvironment.IsEnvironment(AppEnvironments.Test);
                }
            }
            
            public static class AppEnvironments
            {
                public static readonly string Test = nameof(Test);
            }

            2.Startup 方法約定

            上面的方式把不同環境的代碼放在了同一個方法中,看起來比較混亂也不容易區分。因此我們希望ConfigureServicesConfigure能夠根據不同的環境進行代碼拆分。

            我們可以通過方法命名約定來解決,約定Configure{EnvironmentName}ServicesConfigure{EnvironmentName}Services來裝載不同環境的代碼。如果當前環境沒有對應的方法,則使用原來的ConfigureServicesConfigure方法。

            我就只拿 Development 和 Production 舉例了

            public class Startup
            {
                // 我這里注入 IWebHostEnvironment,僅僅是為了打印出來當前環境信息
                public Startup(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
                {
                    Configuration = configuration;
                    WebHostEnvironment = webHostEnvironment;
                }
            
                public IConfiguration Configuration { get; }
            
                public IWebHostEnvironment WebHostEnvironment { get; }
            
                #region ConfigureServices
                private void StartupConfigureServices(IServiceCollection services)
                {
                    Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}");
                }
            
                public void ConfigureDevelopmentServices(IServiceCollection services)
                {
                    StartupConfigureServices(services);
                }
            
                public void ConfigureProductionServices(IServiceCollection services)
                {
                    StartupConfigureServices(services);
                }
            
                public void ConfigureServices(IServiceCollection services)
                {
                    StartupConfigureServices(services);
                }
                #endregion
            
                #region Configure
                private void StartupConfigure(IApplicationBuilder app)
                {
                    Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}");
                }
            
                public void ConfigureDevelopment(IApplicationBuilder app)
                {
                    StartupConfigure(app);
                }
            
                public void ConfigureProduction(IApplicationBuilder app)
                {
                    StartupConfigure(app);
                }
            
                public void Configure(IApplicationBuilder app)
                {
                    StartupConfigure(app);
                } 
                #endregion
            }

            3.Startup 類約定

            該方式適用于多環境下,代碼差異較大的情況。

            程序啟動時,會優先尋找當前環境命名符合Startup{EnvironmentName}的 Startup 類,如果找不到,則使用名稱為Startup的類

            首先,CreateHostBuilder方法需要做一處修改

            public static IHostBuilder CreateHostBuilder(string[] args) =>
                Host.CreateDefaultBuilder(args)
                    .ConfigureWebHostDefaults(webBuilder =>
                    {
                        //webBuilder.UseStartup<Startup>();
            
                        webBuilder.UseStartup(typeof(Startup).GetTypeInfo().Assembly.FullName);
                    });

            接下來,就是為各個環境定義 Startup 類了(我就只拿 Development 和 Production 舉例了)

            public class StartupDevelopment
            {
                // 我這里注入 IWebHostEnvironment,僅僅是為了打印出來當前環境信息
                public StartupDevelopment(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
                {
                    Configuration = configuration;
                    WebHostEnvironment = webHostEnvironment;
                }
            
                public IConfiguration Configuration { get; }
            
                public IWebHostEnvironment WebHostEnvironment { get; }
            
                public void ConfigureServices(IServiceCollection services)
                {
                    Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}");
                }
            
                public void Configure(IApplicationBuilder app)
                {
                    Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}");
                }
            }
            
            public class StartupProduction
            {
                public StartupProduction(IConfiguration configuration, IWebHostEnvironment webHostEnvironment)
                {
                    Configuration = configuration;
                    WebHostEnvironment = webHostEnvironment;
                }
            
                public IConfiguration Configuration { get; }
            
                public IWebHostEnvironment WebHostEnvironment { get; }
            
                public void ConfigureServices(IServiceCollection services)
                {
                    Console.WriteLine($"{nameof(ConfigureServices)}: {WebHostEnvironment.EnvironmentName}");
                }
            
                public void Configure(IApplicationBuilder app)
                {
                    Console.WriteLine($"{nameof(Configure)}: {WebHostEnvironment.EnvironmentName}");
                }
            }

            到此這篇關于理解ASP.NET Core 啟動類(Startup)的文章就介紹到這了,更多相關ASP.NET Core Startup內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持腳本之家!

            來源:http://www.jb51.net/article/221625.htm