一個典型的ASP.NET Core應用程序會包含Program與Startup兩個文件。Program類中有應用程序的入口方法Main,其中的處理邏輯一般是建立一個WebHostBuilder,再生成WebHost,而後啓動項目。app
1 public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>();
UseStartup方法的實現內容:async
1 public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType) 2 { 3 var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name; 4 5 return hostBuilder 6 .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName) 7 .ConfigureServices(services => 8 { 9 if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo())) 10 { 11 services.AddSingleton(typeof(IStartup), startupType); 12 } 13 else 14 { 15 services.AddSingleton(typeof(IStartup), sp => 16 { 17 var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>(); 18 return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName)); 19 }); 20 } 21 }); 22 }
能夠看出所指定的Startup類型會在DI容器中註冊爲單例形式,註冊的處理過程被封裝成Actionide
同時Startup類型能夠有兩種實現方式:ui
實際使用的Startup類常常是這樣的:this
1 public class Startup 2 { 3 public Startup(IConfiguration configuration) 4 { 5 Configuration = configuration; 6 } 7 8 public IConfiguration Configuration { get; } 9 10 // This method gets called by the runtime. Use this method to add services to the container. 11 public void ConfigureServices(IServiceCollection services) 12 { 13 } 14 15 // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 16 public void Configure(IApplicationBuilder app, IHostingEnvironment env) 17 { 18 } 19 }
因此明顯不是第一種方式,那麼其又是怎麼與第二種方式關聯起來的?ConventionBasedStartup的構造方法中傳入了StartupMethods類型的參數,它的LoadMethod方法裏曝露了更多的信息。spa
1 public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName) 2 { 3 var configureMethod = FindConfigureDelegate(startupType, environmentName); 4 5 var servicesMethod = FindConfigureServicesDelegate(startupType, environmentName); 6 var configureContainerMethod = FindConfigureContainerDelegate(startupType, environmentName); 7 8 object instance = null; 9 if (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic)) 10 { 11 instance = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType); 12 } 13 14 // The type of the TContainerBuilder. If there is no ConfigureContainer method we can just use object as it's not 15 // going to be used for anything. 16 var type = configureContainerMethod.MethodInfo != null ? configureContainerMethod.GetContainerType() : typeof(object); 17 18 var builder = (ConfigureServicesDelegateBuilder) Activator.CreateInstance( 19 typeof(ConfigureServicesDelegateBuilder<>).MakeGenericType(type), 20 hostingServiceProvider, 21 servicesMethod, 22 configureContainerMethod, 23 instance); 24 25 return new StartupMethods(instance, configureMethod.Build(instance), builder.Build()); 26 }
它的內部處理中會找尋三種方法,ConfigureServices, Configure與ConfigureContainer。3d
1 private static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName) 2 { 3 var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true); 4 return new ConfigureBuilder(configureMethod); 5 } 6 7 private static ConfigureContainerBuilder FindConfigureContainerDelegate(Type startupType, string environmentName) 8 { 9 var configureMethod = FindMethod(startupType, "Configure{0}Container", environmentName, typeof(void), required: false); 10 return new ConfigureContainerBuilder(configureMethod); 11 } 12 13 private static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName) 14 { 15 var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false) 16 ?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false); 17 return new ConfigureServicesBuilder(servicesMethod); 18 }
ConfigureServices方法用於在容器中註冊各類所需使用的服務接口類型,Configure方法中可使用各類middleware(中間件),對HTTP請求pipeline(管道)進行配置。code
這兩者與IStartup接口的方法基本一致,而ConfigureContainer方法則是提供了一種引入第三方DI容器的功能。component
1 public interface IStartup 2 { 3 IServiceProvider ConfigureServices(IServiceCollection services); 4 5 void Configure(IApplicationBuilder app); 6 }
使用第二種Startup類型的實現方式時,對於Configure方法的參數也能夠更加靈活,不只能夠傳入IApplicationBuilder類型,還能夠有其它已註冊過的任意接口類型。中間件
ConfigureBuilder類的Build方法對額外參數的處理方法簡單明瞭,是IApplicationBuilder類型的直接傳入參數實例,不是的則從DI容器中獲取實例:
1 public class ConfigureBuilder 2 { 3 public Action<IApplicationBuilder> Build(object instance) => builder => Invoke(instance, builder); 4 5 private void Invoke(object instance, IApplicationBuilder builder) 6 { 7 // Create a scope for Configure, this allows creating scoped dependencies 8 // without the hassle of manually creating a scope. 9 using (var scope = builder.ApplicationServices.CreateScope()) 10 { 11 var serviceProvider = scope.ServiceProvider; 12 var parameterInfos = MethodInfo.GetParameters(); 13 var parameters = new object[parameterInfos.Length]; 14 for (var index = 0; index < parameterInfos.Length; index++) 15 { 16 var parameterInfo = parameterInfos[index]; 17 if (parameterInfo.ParameterType == typeof(IApplicationBuilder)) 18 { 19 parameters[index] = builder; 20 } 21 else 22 { 23 try 24 { 25 parameters[index] = serviceProvider.GetRequiredService(parameterInfo.ParameterType); 26 } 27 ... 28 } 29 } 30 MethodInfo.Invoke(instance, parameters); 31 } 32 } 33 }
此外,在找尋各類方法的處理中能夠看到環境變量的身影。因此用第二種方式能夠依據不一樣的環境變量定義同類型但不一樣名稱的方法,這樣能夠省去寫很多if...else...
的處理。UseStartup方法中還只是申明瞭須要註冊Startup類型,實際的調用是在WebHostBuilder類執行Build方法時發生的。
1 private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors) 2 { 3 ... 4 5 foreach (var configureServices in _configureServicesDelegates) 6 { 7 configureServices(_context, services); 8 } 9 10 return services; 11 }
至於Startup類型中方法的調用時機,則需跟蹤到WebHost類中。
首先是它的Initialize方法會確保得到Startup類的實例,並調用ConfigureServices方法註冊服務接口。
1 private void EnsureApplicationServices() 2 { 3 if (_applicationServices == null) 4 { 5 EnsureStartup(); 6 _applicationServices = _startup.ConfigureServices(_applicationServiceCollection); 7 } 8 } 9 10 private void EnsureStartup() 11 { 12 if (_startup != null) 13 { 14 return; 15 } 16 17 _startup = _hostingServiceProvider.GetService<IStartup>(); 18 19 ... 20 }
而後WebHost實例被啓動時,它的BuildApplication方法會建立一個ApplicationBuilder實例,以其做爲Configure方法參數,同時調用Configure方法,這時Configure方法中對那些middleware的處理方式(即Func<RequestDelegate, RequestDelegate>方法)會被加入到ApplicationBuilder之中。
1 private RequestDelegate BuildApplication() 2 { 3 try 4 { 5 _applicationServicesException?.Throw(); 6 EnsureServer(); 7 8 var builderFactory = _applicationServices.GetRequiredService<IApplicationBuilderFactory>(); 9 var builder = builderFactory.CreateBuilder(Server.Features); 10 builder.ApplicationServices = _applicationServices; 11 12 var startupFilters = _applicationServices.GetService<IEnumerable<IStartupFilter>>(); 13 Action<IApplicationBuilder> configure = _startup.Configure; 14 foreach (var filter in startupFilters.Reverse()) 15 { 16 configure = filter.Configure(configure); 17 } 18 19 configure(builder); 20 21 return builder.Build(); 22 } 23 }
ApplicationBuilder的Build方法將這些處理邏輯嵌套起來,最底層的是返回404未找到的處理邏輯。
1 public RequestDelegate Build() 2 { 3 RequestDelegate app = context => 4 { 5 context.Response.StatusCode = 404; 6 return Task.CompletedTask; 7 }; 8 9 foreach (var component in _components.Reverse()) 10 { 11 app = component(app); 12 } 13 14 return app; 15 }
BuildApplication方法返回已嵌套的RequestDelegate委託方法,並在以後生成的HostingApplication實例中,將其傳入它的構造方法。
1 public virtual async Task StartAsync(CancellationToken cancellationToken = default) 2 { 3 HostingEventSource.Log.HostStart(); 4 _logger = _applicationServices.GetRequiredService<ILogger<WebHost>>(); 5 _logger.Starting(); 6 7 var application = BuildApplication(); 8 9 _applicationLifetime = _applicationServices.GetRequiredService<IApplicationLifetime>() as ApplicationLifetime; 10 _hostedServiceExecutor = _applicationServices.GetRequiredService<HostedServiceExecutor>(); 11 var diagnosticSource = _applicationServices.GetRequiredService<DiagnosticListener>(); 12 var httpContextFactory = _applicationServices.GetRequiredService<IHttpContextFactory>(); 13 var hostingApp = new HostingApplication(application, _logger, diagnosticSource, httpContextFactory); 14 await Server.StartAsync(hostingApp, cancellationToken).ConfigureAwait(false); 15 16 }
上述方法中HostingApplication實例最終被傳入KestrelServer的啓動方法中,這樣才能在其內部調用HostingApplication的ProcessRequestAsync方法,並開始層層調用諸多的RequestDelegate方法。
1 public Task ProcessRequestAsync(Context context) 2 { 3 return _application(context.HttpContext); 4 }
若是以爲使用Startup類仍是有點麻煩的話,直接使用WebHostBuilder所提供的擴展方法也是一樣的效果。
1 public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 2 WebHost.CreateDefaultBuilder(args) 3 .ConfigureAppConfiguration((hostingContext, config) => 4 { 5 HostingEnvironment = hostingContext.HostingEnvironment; 6 Configuration = config.Build(); 7 }) 8 .ConfigureServices(services => 9 { 10 services.AddMvc(); 11 }) 12 .Configure(app => 13 { 14 if (HostingEnvironment.IsDevelopment()) 15 { 16 app.UseDeveloperExceptionPage(); 17 } 18 else 19 { 20 app.UseExceptionHandler("/Error"); 21 } 22 23 app.UseMvcWithDefaultRoute(); 24 app.UseStaticFiles(); 25 });
不過使用獨立的Startup類有着額外的好處,Startup類能夠被包含在與Program類不用的的程序集中。而後經過WebHostOptions類中StartupAssembly屬性的設定及其它相關處理,完成UseStartup方法一樣的功能。
1 private IServiceCollection BuildCommonServices(out AggregateException hostingStartupErrors) 2 { 3 ... 4 5 if (!string.IsNullOrEmpty(_options.StartupAssembly)) 6 { 7 try 8 { 9 var startupType = StartupLoader.FindStartupType(_options.StartupAssembly, _hostingEnvironment.EnvironmentName); 10 11 if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo())) 12 { 13 services.AddSingleton(typeof(IStartup), startupType); 14 } 15 else 16 { 17 services.AddSingleton(typeof(IStartup), sp => 18 { 19 var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>(); 20 var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName); 21 return new ConventionBasedStartup(methods); 22 }); 23 } 24 } 25 ... 26 } 27 28 ... 29 30 return services; 31 }