今天我們來結合原始碼來探究一下ASP.NET CORE Web框架的運行原理,
可以先整體看一下下面這張基于原始碼分析程序的一個總結大綱,包含各環節完成的關鍵步驟:

下面我們將一起來結合原始碼探索啟動一個ASP.NET CORE的Web專案時框架是怎么運行起來的,以及各個環節框架底層的原始碼大致做了哪些事情!
一、初始化與框架配置
首先我們聚焦于Host.CreateDefaultBuilder
1 public static IHostBuilder CreateDefaultBuilder(string[] args) 2 { 3 //使用默認的配置初始化一個HostBuilder物件 4 var builder = new HostBuilder(); 5 6 builder.UseContentRoot(Directory.GetCurrentDirectory()); 7 builder.ConfigureHostConfiguration(config => 8 { 9 config.AddEnvironmentVariables(prefix: "DOTNET_"); 10 if (args != null) 11 { 12 config.AddCommandLine(args); 13 } 14 }); 15 16 builder.ConfigureAppConfiguration((hostingContext, config) => 17 { 18 var env = hostingContext.HostingEnvironment; 19 20 config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) 21 .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); 22 23 if (env.IsDevelopment() && !string.IsNullOrEmpty(env.ApplicationName)) 24 { 25 var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); 26 if (appAssembly != null) 27 { 28 config.AddUserSecrets(appAssembly, optional: true); 29 } 30 } 31 32 config.AddEnvironmentVariables(); 33 34 if (args != null) 35 { 36 config.AddCommandLine(args); 37 } 38 }) 39 .ConfigureLogging((hostingContext, logging) => 40 { 41 var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 42 43 // IMPORTANT: This needs to be added *before* configuration is loaded, this lets 44 // the defaults be overridden by the configuration. 45 if (isWindows) 46 { 47 // Default the EventLogLoggerProvider to warning or above 48 logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning); 49 } 50 51 logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); 52 logging.AddConsole(); 53 logging.AddDebug(); 54 logging.AddEventSourceLogger(); 55 56 if (isWindows) 57 { 58 // Add the EventLogLoggerProvider on windows machines 59 logging.AddEventLog(); 60 } 61 }) 62 .UseDefaultServiceProvider((context, options) => 63 { 64 var isDevelopment = context.HostingEnvironment.IsDevelopment(); 65 options.ValidateScopes = isDevelopment; 66 options.ValidateOnBuild = isDevelopment; 67 }); 68 69 return builder; 70 }View Code
該方法首先會創建一個IHostBuilder,并使用一些默認的配置進行初始化:
UseContentRoot 設定主機專案根目錄
ConfigureHostConfiguration配置環境變數和命令列引數添加到Configure物件,便于程式在以后的運行中可以從Configure物件獲取到來源于環境變數和命令列的引數
ConfigureAppConfiguration設定對組態檔和用戶機密檔案的加載,并且可以再次使用該方法來定義App應用的配置資訊,對于Configure的講解可查看我的這篇文章《淺析.netcore中的Configuration》
ConfigureLogging用來配置系統的日志
UseDefaultServiceProvider用來配置框架使用默認的IOC容器
然后程式來到了ConfigureWebHostDefaults方法
程式會先使用GenericWebHostBuilder 創建和初始化一個IWebhostBuilder 物件,然后呼叫WebHost.ConfigureWebDefaults方法,在方法中通過呼叫UseKestrel告訴框架使用Kestrel作為web服務器用于接受請求,完成Kestrel服務器的相關配置,

接著程式會呼叫webBuilder.UseStartup<Startup>(),該方法會找出Startup中的ConfigureServices、ConfigureContainer和Configure方法,將其方法的實際執行動作呼叫IHostBuilder中的ConfigureServices方法以委托的形式保存起來,下面為UseStartUp執行的部分源代碼
1 public IWebHostBuilder UseStartup(Type startupType) 2 { 3 // UseStartup can be called multiple times. Only run the last one. 4 _builder.Properties["UseStartup.StartupType"] = startupType; 5 _builder.ConfigureServices((context, services) => 6 { 7 if (_builder.Properties.TryGetValue("UseStartup.StartupType", out var cachedType) && (Type)cachedType == startupType) 8 { 9 UseStartup(startupType, context, services); 10 } 11 }); 12 13 return this; 14 } 15 16 private void UseStartup(Type startupType, HostBuilderContext context, IServiceCollection services) 17 { 18 var webHostBuilderContext = GetWebHostBuilderContext(context); 19 var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)]; 20 21 ... 22 try 23 { 24 //1.完成對StartUp類的合法性進行校驗 25 // We cannot support methods that return IServiceProvider as that is terminal and we need ConfigureServices to compose 26 if (typeof(IStartup).IsAssignableFrom(startupType)) 27 { 28 throw new NotSupportedException($"{typeof(IStartup)} isn't supported"); 29 } 30 if (StartupLoader.HasConfigureServicesIServiceProviderDelegate(startupType, context.HostingEnvironment.EnvironmentName)) 31 { 32 throw new NotSupportedException($"ConfigureServices returning an {typeof(IServiceProvider)} isn't supported."); 33 } 34 35 instance = ActivatorUtilities.CreateInstance(new HostServiceProvider(webHostBuilderContext), startupType); 36 context.Properties[_startupKey] = instance; 37 38 //2.查找并校驗StartUp類中的ConfigureServices方法 39 // Startup.ConfigureServices 40 var configureServicesBuilder = StartupLoader.FindConfigureServicesDelegate(startupType, context.HostingEnvironment.EnvironmentName); 41 var configureServices = configureServicesBuilder.Build(instance); 42 43 //3.執行StartUp類中的ConfigureServices 44 configureServices(services); 45 46 //4.查找并校驗StartUp類中的ConfigureContainer方法 47 // REVIEW: We're doing this in the callback so that we have access to the hosting environment 48 // Startup.ConfigureContainer 49 var configureContainerBuilder = StartupLoader.FindConfigureContainerDelegate(startupType, context.HostingEnvironment.EnvironmentName); 50 ... 51 52 //5.查找并校驗StartUp類中的Configure方法 53 // Resolve Configure after calling ConfigureServices and ConfigureContainer 54 configureBuilder = StartupLoader.FindConfigureDelegate(startupType, context.HostingEnvironment.EnvironmentName); 55 } 56 catch (Exception ex) when (webHostOptions.CaptureStartupErrors) 57 { 58 startupError = ExceptionDispatchInfo.Capture(ex); 59 } 60 61 ... 62 }View Code
有些同學可能已經發現,我們可以使用UseStartup的時候可以不指定具體的StartUp類,而是指定一個程式集便可實作通過定義不同的環境去加載不同的StartUp類,如下圖我們定義了多個StartUp類

然后將webBuilder.UseStartup<Startup>()改為webBuilder.UseStartup(typeof(Startup).GetTypeInfo().Assembly.FullName),通過設定不同的環境配置項便可執行對應的StartUp

這個又是怎么實作的呢?這便要說起前面我們使用GenericWebHostBuilder構造IWebHostBuilder物件的時候,建構式中會拿到傳遞到程式集字串集合當前的主機環境找到對應的StartUp類,最后的執行與直接使用webBuilder.UseStartup<Startup>()最后呼叫的邏輯是一樣的,所以看到原始碼之后,就會對于這樣的寫法和用途醍醐灌頂,
1 public GenericWebHostBuilder(IHostBuilder builder) 2 { 3 _builder = builder; 4 5 ... 6 7 _builder.ConfigureServices((context, services) => 8 { 9 var webhostContext = GetWebHostBuilderContext(context); 10 var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)]; 11 12 ... 13 14 // webHostOptions.StartupAssembly拿到的就是UseStartUp傳遞的程式集字串 15 // Support UseStartup(assemblyName) 16 if (!string.IsNullOrEmpty(webHostOptions.StartupAssembly)) 17 { 18 try 19 { 20 //根據Environment找到對應的StartUp類 21 var startupType = StartupLoader.FindStartupType(webHostOptions.StartupAssembly, webhostContext.HostingEnvironment.EnvironmentName); 22 //webBuilder.UseStartup<Startup>()呼叫程序中也會執行此方法,實作的作用在上面有進行說明 23 UseStartup(startupType, context, services); 24 } 25 catch (Exception ex) when (webHostOptions.CaptureStartupErrors) 26 { 27 } 28 } 29 }); 30 }View Code
二、IHostBuilder.Build()構建裝載框架的處理能力
到此為止,前面的動作都是使用默認的配置完成了一些物件的初始化,并且將一些執行動作保存為委托,并沒有實際執行,等到來到Build方法,會執行方法內部的三個核心動作,
1 public IHost Build() 2 { 3 ... 4 BuildHostConfiguration(); 5 ... 6 BuildAppConfiguration(); 7 CreateServiceProvider(); 8 9 ... 10 } 11 12 private void BuildHostConfiguration() 13 { 14 var configBuilder = new ConfigurationBuilder() 15 .AddInMemoryCollection(); // Make sure there's some default storage since there are no default providers 16 17 foreach (var buildAction in _configureHostConfigActions) 18 { 19 buildAction(configBuilder); 20 } 21 _hostConfiguration = configBuilder.Build(); 22 } 23 24 private void BuildAppConfiguration() 25 { 26 var configBuilder = new ConfigurationBuilder() 27 .SetBasePath(_hostingEnvironment.ContentRootPath) 28 .AddConfiguration(_hostConfiguration, shouldDisposeConfiguration: true); 29 30 foreach (var buildAction in _configureAppConfigActions) 31 { 32 buildAction(_hostBuilderContext, configBuilder); 33 } 34 _appConfiguration = configBuilder.Build(); 35 _hostBuilderContext.Configuration = _appConfiguration; 36 } 37 38 private void CreateServiceProvider() 39 { 40 var services = new ServiceCollection(); 41 .... //注冊框架需要的服務實體到容器 42 43 foreach (var configureServicesAction in _configureServicesActions) 44 { 45 configureServicesAction(_hostBuilderContext, services); 46 } 47 48 var containerBuilder = _serviceProviderFactory.CreateBuilder(services); 49 50 foreach (var containerAction in _configureContainerActions) 51 { 52 containerAction.ConfigureContainer(_hostBuilderContext, containerBuilder); 53 } 54 55 _appServices = _serviceProviderFactory.CreateServiceProvider(containerBuilder); 56 57 if (_appServices == null) 58 { 59 throw new InvalidOperationException($"The IServiceProviderFactory returned a null IServiceProvider."); 60 } 61 62 // resolve configuration explicitly once to mark it as resolved within the 63 // service provider, ensuring it will be properly disposed with the provider 64 _ = _appServices.GetService<IConfiguration>(); 65 }View Code
BuildHostConfiguration會執行前面呼叫ConfigureHostConfiguration保存的委托方法完成對Host的配置
BuildAppConfiguration會執行前面呼叫ConfigureAppConfiguration保存的委托方法完成對App應用的配置
CreateServiceProvider首先會初始化ServiceCollection的容器實體,然后將部分框架需要的實體物件添加到容器中,完成主機環境、背景關系、生命周期等相關物件的容器注冊,隨后會執行前面呼叫ConfigureServices保存的委托方法,所以StartUp類中的ConfigureServices其實就是在這個環節執行的,
三、IHost.Run()組裝請求處理流程并啟動服務器監聽
最后來到IHost.Run去運行主機
1 //Run最侄訓呼叫該方法 2 public async Task StartAsync(CancellationToken cancellationToken) 3 { 4 ..... 5 6 RequestDelegate application = null; 7 8 try 9 { 10 //這邊是前面拿到的StartUp中的Configure方法 11 Action<IApplicationBuilder> configure = Options.ConfigureApplication; 12 13 if (configure == null) 14 { 15 throw new InvalidOperationException($"No application configured. Please specify an application via IWebHostBuilder.UseStartup, IWebHostBuilder.Configure, or specifying the startup assembly via {nameof(WebHostDefaults.StartupAssemblyKey)} in the web host configuration."); 16 } 17 18 var builder = ApplicationBuilderFactory.CreateBuilder(Server.Features); 19 20 .... 21 22 configure(builder); 23 24 // Build the request pipeline 25 application = builder.Build(); 26 } 27 catch (Exception ex) 28 { 29 } 30 31 var httpApplication = new HostingApplication(application, Logger, DiagnosticListener, HttpContextFactory); 32 33 await Server.StartAsync(httpApplication, cancellationToken); 34 35 ... 36 }View Code
該程序中框架會拿到執行前面查詢StartUp找到并保存起來的Configure方法,此方法會定義并添加對請求進行處理的管道中間件以及中間間的處理順序,然后會通過ApplicationBuilderFactory創建一個IApplicationBuilder物件,使用IApplicationBuilder.build去組裝管道處理流程,最后生成一個RequestDelegate,該物件就是我們的處理管道物件,
然后程式會把RequestDelegate進行包裝后作為引數傳遞到Server.StartAsync方法中,Server物件即為Kestrel服務器,呼叫Server.StartAsync便會啟動Kestrel服務器,實作站點對請求的監聽,同時通過傳遞過來的RequestDelegate物件,便把Kestrel與管道中間件連接起來了,從而實作了從請求監聽到請求處理的全程序,
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/441885.html
標籤:.NET Core
上一篇:WEB端播放華為海康大華視頻方案
下一篇:.NET中的HashSet
