.NET Core开发日志——Startup

一个典型的ASP.NET Core应用程序会包含Program与Startup两个文件。Program类中有应用程序的入口方法Main,其中的处理逻辑通常是创建一个WebHostBuilder,再生成WebHost,最后启动之。

而在创建WebHostBuilder时又会常常会指定一个Startup类型。

public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>();

这个Startup类里究竟做了哪些事情呢?

查一下UseStartup方法的实现内容:

public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType) { var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name; return hostBuilder .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName) .ConfigureServices(services => { if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo())) { services.AddSingleton(typeof(IStartup), startupType); } else { services.AddSingleton(typeof(IStartup), sp => { var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>(); return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName)); }); } }); }

可以看出所指定的Startup类型会在DI容器中注册为单例形式,注册的处理过程被封装成Action

同时Startup类型可以有两种实现方式:

自定义实现IStartup接口的类

内部定义的ConventionBasedStartup类

实际使用的Startup类经常是这样的:

public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { ... } public void Configure(IApplicationBuilder app, IHostingEnvironment env) { ... } }

所以明显不是第一种方式,那么其又是怎么与第二种方式关联起来的?

ConventionBasedStartup的构造方法中传入了StartupMethods类型的参数,它的LoadMethod方法里曝露了更多的信息。

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()); }

它的内部处理中会找寻三种方法,ConfigureServices, Configure与ConfigureContainer。

private static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName) { var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true); return new ConfigureBuilder(configureMethod); } private static ConfigureContainerBuilder FindConfigureContainerDelegate(Type startupType, string environmentName) { var configureMethod = FindMethod(startupType, "Configure{0}Container", environmentName, typeof(void), required: false); return new ConfigureContainerBuilder(configureMethod); } private static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName) { var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false) ?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false); return new ConfigureServicesBuilder(servicesMethod); }

ConfigureServices方法用于在容器中注册各种所需使用的服务接口类型,Configure方法中可以使用各种middleware(中间件),对HTTP请求pipeline(管道)进行配置。
这二者与IStartup接口的方法基本一致,而ConfigureContainer方法则是提供了一种引入第三方DI容器的功能。

public interface IStartup { IServiceProvider ConfigureServices(IServiceCollection services); void Configure(IApplicationBuilder app); }

使用第二种Startup类型的实现方式时,对于Configure方法的参数也可以更加灵活,不仅可以传入IApplicationBuilder类型,还可以有其它已注册过的任意接口类型。

ConfigureBuilder类的Build方法对额外参数的处理方法简单明了,是IApplicationBuilder类型的直接传入参数实例,不是的则从DI容器中获取实例:

public class ConfigureBuilder { public Action<IApplicationBuilder> Build(object instance) => builder => Invoke(instance, builder); private void Invoke(object instance, IApplicationBuilder builder) { // Create a scope for Configure, this allows creating scoped dependencies // without the hassle of manually creating a scope. using (var scope = builder.ApplicationServices.CreateScope()) { var serviceProvider = scope.ServiceProvider; var parameterInfos = MethodInfo.GetParameters(); var parameters = new object[parameterInfos.Length]; for (var index = 0; index < parameterInfos.Length; index++) { var parameterInfo = parameterInfos[index]; if (parameterInfo.ParameterType == typeof(IApplicationBuilder)) { parameters[index] = builder; } else { try { parameters[index] = serviceProvider.GetRequiredService(parameterInfo.ParameterType); } ... } } MethodInfo.Invoke(instance, parameters); } } }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zyxysp.html