---恢復內容開始---
筆者從事netcore相關項目開發已經大半年了,從netcore 1.0到現在3.0大概經過了3年左右的時間,記得netcore剛出來的時候國內相關的學習資料缺乏,限制于外語不大熟練的限制國外的相關書籍看起來相當吃力,于是在當當網上買了一本價值70多的入門書籍,買回來卻發現內容都是掛羊頭賣狗肉,深深地鄙視這些為了賺錢不顧內容的作者。如今網上相關的學習資料也相當多,筆者也趁著現在不忙,再來學習一下aspnetcore的源碼,文章中所用的源碼版本是3.0,如果讀者下的源碼是3.0以下,有些函數會有所區別。
下圖是筆者整理的一個簡單類圖,以助自己理解源碼。
對象介紹
WebHostBuilder:負責初始化環境變量,默認設置,指定startup類,創建servicecollection,讀取configuration,創建基礎服務并注入到DI,加載主機配置(hostingStartup),最主要的創建webhost。
WebHost:站點主機,加載應用服務,加載應用中間件,開始和停止站點
WebHostBuilderContext: 上下文,包含環境變量和默認值
WebHostOptions: 創建webhost的時候使用的參數
ServiceCollection: 所有服務的儲存的集合,添加刪除服務
serviceprovider: 獲得服務的實例
serviceDescriptor: 服務的描述類,所有的服務最后都是轉化成該類后注入DI
?
?
關鍵的對象介紹完了,下面我們來看一下web站點是如何運行起來的。
- ?main函數中創建WebHostBuilder對象
- 指定startup?
? ? ?
public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType){var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name;hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName);// Light up the GenericWebHostBuilder implementationif (hostBuilder is ISupportsStartup supportsStartup){return supportsStartup.UseStartup(startupType);}return hostBuilder.ConfigureServices(services =>{if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo())){services.AddSingleton(typeof(IStartup), startupType);}else{services.AddSingleton(typeof(IStartup), sp =>{var hostingEnvironment = sp.GetRequiredService<IHostEnvironment>();return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));});}});}
從代碼可以看到,如果這個startup是繼承自IStartup,直接注入到DI,如果不是,則用ConventtionBaseStartup進行包裝,而該類是繼承自IStartup。微軟默認的startup類不繼承IStartup的,具體的實現細節和原因在后面會具體說明。
- 加載系統的默認的配置文件,可以通過?ConfigureAppConfiguration擴展方法將自己的配置文件加載到容器里面
public static IWebHostBuilder ConfigureAppConfiguration(this IWebHostBuilder hostBuilder, Action<IConfigurationBuilder> configureDelegate){return hostBuilder.ConfigureAppConfiguration((context, builder) => configureDelegate(builder));}
- 創建WebHostBuild上下文
- 加載web主機配置,尋找程序集中貼有HostingStartupAttribute標簽的類并調用Configure方法,在WebHost實例化之前預留的鉤子,由于筆者也沒有用到過這個功能,就不多贅述了。
foreach (var assemblyName in _options.GetFinalHostingStartupAssemblies().Distinct(StringComparer.OrdinalIgnoreCase)){try{var assembly = Assembly.Load(new AssemblyName(assemblyName));foreach (var attribute in assembly.GetCustomAttributes<HostingStartupAttribute>()){var hostingStartup = (IHostingStartup)Activator.CreateInstance(attribute.HostingStartupType);hostingStartup.Configure(this);}}catch (Exception ex){...}}
- 創建servicecollection 類,此類存在于整個webhost生命周期,所有的應用服務和系統服務都存儲在該類中
- 添加系統服務,包括環境變量,日志服務,配置服務,監聽服務等等
var services = new ServiceCollection();services.AddSingleton(_options);services.AddSingleton<IWebHostEnvironment>(_hostingEnvironment);services.AddSingleton<IHostEnvironment>(_hostingEnvironment);
#pragma warning disable CS0618 // Type or member is obsoleteservices.AddSingleton<AspNetCore.Hosting.IHostingEnvironment>(_hostingEnvironment);services.AddSingleton<Extensions.Hosting.IHostingEnvironment>(_hostingEnvironment);
#pragma warning restore CS0618 // Type or member is obsoleteservices.AddSingleton(_context);var builder = new ConfigurationBuilder().SetBasePath(_hostingEnvironment.ContentRootPath).AddConfiguration(_config);_configureAppConfigurationBuilder?.Invoke(_context, builder);var configuration = builder.Build();services.AddSingleton<IConfiguration>(configuration);_context.Configuration = configuration;var listener = new DiagnosticListener("Microsoft.AspNetCore");services.AddSingleton<DiagnosticListener>(listener);services.AddSingleton<DiagnosticSource>(listener);services.AddTransient<IApplicationBuilderFactory, ApplicationBuilderFactory>();services.AddTransient<IHttpContextFactory, DefaultHttpContextFactory>();services.AddScoped<IMiddlewareFactory, MiddlewareFactory>();services.AddOptions();services.AddLogging();services.AddTransient<IServiceProviderFactory<IServiceCollection>, DefaultServiceProviderFactory>();
- 創建?ServiceProvider 類,該類依賴于ServiceCollection,并為其中的服務提供實例
- 創建使用?ServiceProvider和?ServiceCollection類創建?webhost實例并初始化實例
var host = new WebHost(applicationServices,hostingServiceProvider,_options,_config,hostingStartupErrors);try{host.Initialize();...return host;}
初始化實例的時候會調用EnsureApplicationServices的方法。看到_startup.ConfigureServices了沒有,對這個就是我們在startup中寫的方法。
private void EnsureApplicationServices(){if (_applicationServices == null){EnsureStartup();_applicationServices = _startup.ConfigureServices(_applicationServiceCollection);}}
?
可是他是如何找到這個方法的呢,再看一下EnsureStartup方法,原來是實例化了我們在存在servicecollection中的startup類,前面我們說過,這個類ConventionBasedStartup封裝了,所以這里獲取的也ConventionBasedStartup類的實例。
private void EnsureStartup(){if (_startup != null){return;}_startup = _hostingServiceProvider.GetService<IStartup>();...}
再讓我們看下ConventionBasedStartup的結構,實際上就是將該類作為一個代理來調用我們startup類中的方法
public class ConventionBasedStartup : IStartup{private readonly StartupMethods _methods;public ConventionBasedStartup(StartupMethods methods){_methods = methods;}public void Configure(IApplicationBuilder app){try{_methods.ConfigureDelegate(app);}catch (Exception ex){if (ex is TargetInvocationException){ExceptionDispatchInfo.Capture(ex.InnerException).Throw();}throw;}}public IServiceProvider ConfigureServices(IServiceCollection services){try{return _methods.ConfigureServicesDelegate(services);}catch (Exception ex){if (ex is TargetInvocationException){ExceptionDispatchInfo.Capture(ex.InnerException).Throw();}throw;}}}
- 主機實例已創建并且相關服務已初始化完成,這時候就可以start()了。可能你會想可是我的中間件還沒加載,是的 ,start的時候就是加載中間件并創建監聽,讓我們來看一下代碼
public virtual async Task StartAsync(CancellationToken cancellationToken = default){...var application = BuildApplication();...}
private RequestDelegate BuildApplication(){try{_applicationServicesException?.Throw();EnsureServer();var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>();var builder = builderFactory.CreateBuilder(Server.Features);builder.ApplicationServices = _applicationServices;
Action<IApplicationBuilder> configure = _startup.Configure;...configure(builder);return builder.Build();}}
發現代碼中的_startup.Configure了嗎!這個就是調用ConventionBasedStartup這個代理類的c方法,也就是我們startup中的Configure方法。這時候筆者當時也產生了一個疑問,這里的委托只有一個參數,可是在實際應用的時候都會加入很多參數,想這樣public void Configure(IApplicationBuilder app, IHostingEnvironment env,IXxxx xxxx)。猜想是不是初始化ConventionBasedStartup類的時候做了什么封裝,重新回到UseStartup方法
return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));
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());}
哈哈果然 LoadMethods方法將我們寫在StartUp中的方法進行了封裝并創建了一個新的委托,到此我們也就明白為什么官方推薦的startup類不是繼承了Istartup接口的,繼承了接口的類在調用時沒法將DI中的類注入到方法參數中去,需要自己去獲取DI中的實例,筆者設想control類中的注入也是同樣的思想實現的。現在我們的所有中間件和服務已經全部加載進入WebHost中了。
?