Program
我們先看一下1.x和2.x的程式入口項的一個差異
1.x
public class Program { public static void Main(string[] args) { var host = new WebHostBuilder() .UseKestrel() .UseContentRoot(Directory.GetCurrentDirectory()) .UseIISIntegration() .UseStartup<Startup>() .Build(); host.Run(); } }
2.x
public class Program { public static void Main(string[] args) { BuildWebHost(args).Run(); } public static IWebHost BuildWebHost(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>() .Build(); }
2.x對預設配置進行了簡化,把一些基本配置移動了 CreateDefaultBuilder 方法中
public static IWebHostBuilder CreateDefaultBuilder(string[] args) { IWebHostBuilder hostBuilder = new WebHostBuilder()
.UseKestrel((Action<WebHostBuilderContext, KestrelServerOptions>) ((builderContext, options) => options.Configure((IConfiguration) builderContext.Configuration.GetSection("Kestrel"))))
.UseContentRoot(Directory.GetCurrentDirectory())
.ConfigureAppConfiguration((Action<WebHostBuilderContext, IConfigurationBuilder>) ((hostingContext, config) => { IHostingEnvironment hostingEnvironment = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", true, true)
.AddJsonFile("appsettings." + hostingEnvironment.EnvironmentName + ".json", true, true); if (hostingEnvironment.IsDevelopment()) { Assembly assembly = Assembly.Load(new AssemblyName(hostingEnvironment.ApplicationName)); if (assembly != (Assembly) null) config.AddUserSecrets(assembly, true); } config.AddEnvironmentVariables(); if (args == null) return; config.AddCommandLine(args); }))
.ConfigureLogging((Action<WebHostBuilderContext, ILoggingBuilder>) ((hostingContext, logging) => { logging.AddConfiguration((IConfiguration) hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); }))
.ConfigureServices((Action<WebHostBuilderContext, IServiceCollection>) ((hostingContext, services) => { services.PostConfigure<HostFilteringOptions>((Action<HostFilteringOptions>) (options => { if (options.AllowedHosts != null && options.AllowedHosts.Count != 0) return; string str = hostingContext.Configuration["AllowedHosts"]; string[] strArray1; if (str == null) strArray1 = (string[]) null; else strArray1 = str.Split(new char[1]{ ';' }, StringSplitOptions.RemoveEmptyEntries); string[] strArray2 = strArray1; HostFilteringOptions filteringOptions = options; string[] strArray3; if (strArray2 == null || strArray2.Length == 0) strArray3 = new string[1]{ "*" }; else strArray3 = strArray2; filteringOptions.AllowedHosts = (IList<string>) strArray3; })); services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>((IOptionsChangeTokenSource<HostFilteringOptions>) new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration)); services.AddTransient<IStartupFilter, HostFilteringStartupFilter>(); }))
.UseIISIntegration()
.UseDefaultServiceProvider((Action<WebHostBuilderContext, ServiceProviderOptions>) ((context, options) => options.ValidateScopes = context.HostingEnvironment.IsDevelopment())); if (args != null) hostBuilder.UseConfiguration((IConfiguration) new ConfigurationBuilder().AddCommandLine(args).Build()); return hostBuilder; }
這裡我們可以看到在CreateDefaultBuilder生成器中,定義了預設使用的Web伺服器(UseKestrel,使用的是KestrelServer)和一些基礎的配置,包括檔案路徑、應用配置(按appsettings.json,appsettings.{Environment}.json次序載入)、環境變數、日誌,IIS整合等,如果需要的話,還可以指定其他型別的Server(IIS HTTP Server,HTTP.sys Server)和自定義Server(繼承IServer)。
返回到Program中,在獲取到了WebHostBuilder之後緊接著就指定了啟動類UseStartup<Startup>(),Build方法是WebHostBuilder最終的目的,將構造一個WebHost返回,這裡引出了我們在ASP.NET Core - 開篇所說的重要物件:WebHost,並且執行它的Run方法用於啟動應用並開始監聽所有到來的HTTP請求。
Startup
Startup方法用來指定應用程式的啟動類,這裡主要有兩個作用:
- 配置應用需要的服務(服務註冊,ConfigureServices方法)。
- 建立應用的請求處理處理管道(Configure方法)。
public class Startup { private readonly IHostingEnvironment _env; private readonly IConfiguration _config; private readonly ILoggerFactory _loggerFactory; public Startup(IHostingEnvironment env, IConfiguration config, ILoggerFactory loggerFactory) { _env = env; _config = config; _loggerFactory = loggerFactory; } // 注入服務到容器中 public void ConfigureServices(IServiceCollection services) { var logger = _loggerFactory.CreateLogger<Startup>(); if (_env.IsDevelopment()) { // Development service configuration logger.LogInformation("Development environment"); } else { // Non-development service configuration logger.LogInformation($"Environment: {_env.EnvironmentName}"); } ... } // 配置Http請求處理管道 public void Configure(IApplicationBuilder app) { ... } }
Startup 類的 執行順序:構造 -> ConfigureServices -> Configure
1)Startup Constructor(建構函式)
上面的建構函式引出了我們開篇說的三個重要物件:IHostingEnvironment ,IConfiguration ,ILoggerFactory ,這裡先講建構函式的作用,這些物件後面會分篇講。顯而易見,這裡主要是通過依賴注入例項化了該類中需要用到的物件(根據自己的業務),比較簡單
2) ConfigureServices
首先這個方法是可選的,它的引數是IServiceCollection,這也是我們開篇說的重要物件,而且是非常重要的物件,這是一個原生的Ioc容器,所有需要用到的服務都可以註冊到裡面,一般是通過約定風格services.Addxxx, 這樣就可以讓這些服務在應用和Configure方法使用(用來構建管道)。
3)Configure
用於構建管道處理Http請求,管道中的每個中介軟體(Middleware)元件負責請求處理和選擇是否將請求傳遞到管道中的下一個元件,在這裡我們可以新增自己想要的中介軟體來處理每一個Http請求,一般是使用上面的ConfigureServices方法中註冊好的服務,一般的用法是 app.Usexxx,這個Usexxx方法是基於IApplicationBuilder的擴充套件。
需要注意的有三個地方:
- 應儘早在管道中呼叫異常處理委託,這樣就能捕獲在後續管道發生的異常,所以能看到微軟的經典寫法是先把異常處理的中介軟體寫在最前面,這樣方可捕獲稍後呼叫中發生的任何異常。
- 當某個中介軟體不將請求傳遞給下一個中介軟體時,這被稱為“請求管道短路”。 我們通常都會需要短路,這樣可以避免資源浪費,類似與當丟擲異常時我們將不會再往下請求,因為這完全沒有必要:)
- 如果你想某些模組不需要授權就能訪問,應把這些模組放在認證模組前面,所以我們一般會把訪問靜態檔案的中介軟體放在認證模組的前面。
public void Configure(IApplicationBuilder app) { if (env.IsDevelopment()) {// Use the Developer Exception Page to report app runtime errors. app.UseDeveloperExceptionPage(); } else {// Enable the Exception Handler Middleware to catch exceptions // thrown in the following middlewares. app.UseExceptionHandler("/Error"); } // Return static files and end the pipeline. app.UseStaticFiles(); // Use Cookie Policy Middleware to conform to EU General Data // Protection Regulation (GDPR) regulations. app.UseCookiePolicy(); // Authenticate before the user accesses secure resources. app.UseAuthentication(); // If the app uses session state, call Session Middleware after Cookie // Policy Middleware and before MVC Middleware. app.UseSession(); // Add MVC to the request pipeline. app.UseMvc(); }
如果你不想使用Startup類的話,可以使用以下方式配置自己的服務註冊和管道構建,雖然這種方式有點odd :)
public class Program { public static IHostingEnvironment HostingEnvironment { get; set; } public static IConfiguration Configuration { get; set; } public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .ConfigureAppConfiguration((hostingContext, config) => { }) .ConfigureServices(services => { ... }) .Configure(app => { var loggerFactory = app.ApplicationServices .GetRequiredService<ILoggerFactory>(); var logger = loggerFactory.CreateLogger<Program>(); var env = app.ApplicationServices.GetRequiredServices<IHostingEnvironment>(); var config = app.ApplicationServices.GetRequiredServices<IConfiguration>(); logger.LogInformation("Logged in Configure"); if (env.IsDevelopment()) { ... } else { ... } var configValue = config["subsection:suboption1"]; ... }); }
總結
正如ASP.NET Core - 開篇所說的,一個ASP.NET Core應用其實就是一個控制檯應用程式,它在應用啟動時構建一個 Web 伺服器,並且通過指定的Startup類來構建應用服務和請求管道,進而監聽和處理所有的Http請求。