深入探究ASP.NET Core Startup的初始化

前言

    Startup类相信大家都比较熟悉,在我们使用ASP.NET Core开发过程中经常用到的类,我们通常使用它进行IOC服务注册,配置中间件信息等。虽然它不是必须的,但是将这些操作统一在Startup中做处理,会在实际开发中带来许多方便。当我们谈起Startup类的时候你有没有好奇过以下几点

  • 为何我们自定义的Startup可以正常工作。

  • 我们定义的Startup类中ConfigureServices和Configure只能叫这个名字才能被调用到吗?

  • 在使用泛型主机(IHostBuilder)时Startup的构造函数,为何只支持注入IWebHostEnvironment、IHostEnvironment、IConfiguration。

  • ConfigureServices方法为何只能传递IServiceCollection实例。

  • Configure方法的参数为何可以是所有在IServiceCollection注册服务实例。

  • 在ASP.NET Core结合Autofac使用的时候为何我们添加的ConfigureContainer方法会被调用。
    带着以上几点疑问,我们将在本篇文章中探索Startup的源码,来了解Startup初始化过程到底为我们做了些什么。

Startup的另类指定方式

在日常编码过程中,我们通常使用UseStartup的方式来引入Startup类。但是这并不是唯一的方式,还有一种方式是在配置节点中指定Startup所在的程序集来自动查找Startup类,这个我们可以在GenericWebHostBuilder的构造函数源码中的找到相关代码[点击查看源码????]相信熟悉ASP.Net Core启动流程的同学对GenericWebHostBuilder这个类都比较了解。ConfigureWebHostDefaults方法中其实调用了ConfigureWebHost方法,ConfigureWebHost方法中实例化了GenericWebHostBuilder对象,启动流程不是咱们的重点,所以这里只是简单描述一下。直接找到我们需要的代码如下所示

//判断是否配置了StartupAssembly参数
if (!string.IsNullOrEmpty(webHostOptions.StartupAssembly))
{try{//根据你配置的程序集去查找Startupvar startupType = StartupLoader.FindStartupType(webHostOptions.StartupAssembly, webhostContext.HostingEnvironment.EnvironmentName);UseStartup(startupType, context, services);}catch (Exception ex) when (webHostOptions.CaptureStartupErrors){//此处省略代码省略}
}

这里我们可以看出来,我们需要配置StartupAssembly对应的程序集,它可以通过StartupLoader的FindStartupType方法加载程序集中对应的类。我们还可以看到它还传递了EnvironmentName环境变量,至于它起到了什么作用,我们继续往下看。
首先我们需要找到webHostOptions.StartupAssembly是如何被初始化的,在WebHostOptions的构造函数中我们找到了StartupAssembly初始化的地方[点击查看源码????]

StartupAssembly = configuration[WebHostDefaults.StartupAssemblyKey];

从这里也可以看出来它的值来于配置,它的key来自WebHostDefaults.StartupAssemblyKey这个常量值,最后我们找到了的值为

public static readonly string StartupAssemblyKey = "startupAssembly";

也就是说只要我们给startupAssembly配置Startup所在的程序集名称,它就可以在程序集中查找Startup类进行初始化,如下所示

public static IHostBuilder CreateHostBuilder(string[] args) =>Host.CreateDefaultBuilder(args).ConfigureHostConfiguration(config=> {List<KeyValuePair<string, string>> keyValuePairs = new List<KeyValuePair<string, string>>();//配置Startup所在的程序集名称keyValuePairs.Add(new KeyValuePair<string, string>("startupAssembly", "Startup所在的程序集名称"));config.AddInMemoryCollection(keyValuePairs);}).ConfigureWebHostDefaults(webBuilder =>{//这样的话这里就可以省略了//webBuilder.UseStartup<Startup>();});

回到上面的思路,我们在StartupLoader类中查看FindStartupType方法,来看下它是通过什么规则来查找Startup的[点击查看源码????]精简之后的代码大致如下

public static Type FindStartupType(string startupAssemblyName, string environmentName)
{var assembly = Assembly.Load(new AssemblyName(startupAssemblyName));//名称Startup+环境变量的类比如(StartupDevelopment)var startupNameWithEnv = "Startup" + environmentName;//名称为Startup的类var startupNameWithoutEnv = "Startup";// 先查找包含名称Startup+环境变量的相关类,如果找不到则查找名称为Startup的类var type =assembly.GetType(startupNameWithEnv) ??assembly.GetType(startupAssemblyName + "." + startupNameWithEnv) ??assembly.GetType(startupNameWithoutEnv) ??assembly.GetType(startupAssemblyName + "." + startupNameWithoutEnv);if (type == null){// 如果上述规则找不到,则在程序集定义的所有类中继续查找var definedTypes = assembly.DefinedTypes.ToList();var startupType1 = definedTypes.Where(info => info.Name.Equals(startupNameWithEnv, StringComparison.OrdinalIgnoreCase));var startupType2 = definedTypes.Where(info => info.Name.Equals(startupNameWithoutEnv, StringComparison.OrdinalIgnoreCase));var typeInfo = startupType1.Concat(startupType2).FirstOrDefault();if (typeInfo != null){type = typeInfo.AsType();}}//最终返回Startup类型return type;
}

通过上述代码我们可以看到在通过配置指定程序集时是如何查找指定规则的Startup类的,基本上可以理解为先去查找名称为Startup+环境变量的类,如果找不到则继续查找名称为Startup的类,最终会返回Startup的类型传递给UseStartup方法。其实我们最常使用的UseStartup()方法最终也是转换成UseStartup(typeof(T))的方式,所以最终这两种方式走到了相同的地方,接下来我们步入正题,来一起探究一下Starup究竟是如何被初始化的。

Startup的构造函数

相信对Startup有所了解的同学们都比较清楚,在使用泛型主机(IHostBuilder)时Startup的构造函数只支持注入IWebHostEnvironment、IHostEnvironment、IConfiguration,这个在微软官方文档中https://docs.microsoft.com/en-us/aspnet/core/fundamentals/startup?view=aspnetcore-3.1#the-startup-class也有介绍,如果还有不熟悉这个操作的请先反思一下自己,然后在查阅微软官方文档。接下来我们就从源码着手,来探究一下它到底是如何做到的。沿着上述的操作,继续查看UseStartup里的代码找到了如下的实现[点击查看源码????]

//创建Startup实例
object instance = ActivatorUtilities.CreateInstance(new HostServiceProvider(webHostBuilderContext), startupType);

这里的startupType就是我们传递的Startup类型,关于ActivatorUtilities这个类还是比较实用的,它为我们提供了许多帮助我们实例化对象的方法,在日常编程中如果有需要可以使用这个类。上面的ActivatorUtilities的CreateInstance方法的功能就是根据传递IServiceProvider类型的对象去实例化指定的类型对象,我们这里的类型就是startupType。它的使用场景就是,如果某个类型需要用过有参构造函数去实例化,而构造函数的参数可以来自于IServiceProvider的实例,那么使用这个方法就在合适不过了。上面的代码传递的IServiceProvider的实例是HostServiceProvider对象,接下来我们找到它的实现源码[点击查看源码????]代码并不多我们就全部粘贴出来

private class HostServiceProvider : IServiceProvider
{private readonly WebHostBuilderContext _context;public HostServiceProvider(WebHostBuilderContext context){_context = context;}public object GetService(Type serviceType){// 通过这里我们就比较清晰的看出,只有满足这几种情况下才能返回具体的实例,其他的都会返回null#pragma warning disable CS0618 // Type or member is obsoleteif (serviceType == typeof(Microsoft.Extensions.Hosting.IHostingEnvironment)|| serviceType == typeof(Microsoft.AspNetCore.Hosting.IHostingEnvironment)#pragma warning restore CS0618 // Type or member is obsolete|| serviceType == typeof(IWebHostEnvironment)|| serviceType == typeof(IHostEnvironment)){return _context.HostingEnvironment;}if (serviceType == typeof(IConfiguration)){return _context.Configuration;}//不满足这几种情况的类型都返回nullreturn null;}
}

通过这个内部私有类我们就能清晰的看到为何Starup的构造函数只能注入IWebHostEnvironment、IHostEnvironment、IConfiguration相关实例了,HostServiceProvider类实现了IServiceProvider的GetService方法并做了判断,只有满足这几种类型才能返回具体的实例注入,其它不满足条件的类型都会返回null。因此在初始化Starup实例的时候,通过构造函数注入的类型也就只能是这几种了。最终通过这个构造函数初始化了Startup类的实例。

ConfigureServices的装载

接下来我们就来在UseStartup方法里继续查看是如何查找并执行ConfigureServices方法的,继续查看找到如下实现[点击查看源码????]

//传递startupType和环境变量参数查找返回ConfigureServicesBuilder
var configureServicesBuilder = StartupLoader.FindConfigureServicesDelegate(startupType, context.HostingEnvironment.EnvironmentName);
//调用Build方法返回ConfigureServices委托
var configureServices = configureServicesBuilder.Build(instance);
//传递services对象即IServiceCollection对象调用ConfigureServices方法
configureServices(services);

从上述代码中我们可以了解到查找并执行ConfigureServices方法的具体步骤可分为三步,首先在startupType类型中根据环境变量名称查找具体方法返回ConfigureServicesBuilder实例,然后构建ConfigureServicesBuilder实例返回ConfigureServices方法的委托,最后传递IServiceCollection对象执行委托方法。接下来我们就来查看具体实现源码。
我们在StartupLoader类中找到了FindConfigureServicesDelegate方法的相关实现[点击查看源码????]

internal static ConfigureServicesBuilder FindConfigureServicesDelegate(Type startupType, string environmentName)
{//根据startupType和根据environmentName构建的Configure{0}Services字符串先去查找返回类型为IServiceProvider的方法//找不到在查找返回值为void类型的方法var servicesMethod = FindMethod(startupType, "Configure{0}Services", environmentName, typeof(IServiceProvider), required: false)?? FindMethod(startupType, "Configure{0}Services", environmentName, typeof(void), required: false);//根据查找的到的MethodInfo去构建ConfigureServicesBuilder实例return new ConfigureServicesBuilder(servicesMethod);
}

通过这里的源码我们可以看到在startupType类型里去查找名字为environmentName构建的Configure{0}Services的方法信息,然后根据查找的方法信息即MethodInfo对象去构建ConfigureServicesBuilder实例。接下里我们就来查询FindMethod方法的实现

private static MethodInfo FindMethod(Type startupType, string methodName, string environmentName, Type returnType = null, bool required = true)
{//包含环境变量的ConfigureServices方法名称比如(ConfigureDevelopmentServices)var methodNameWithEnv = string.Format(CultureInfo.InvariantCulture, methodName, environmentName);//名为ConfigureServices的方法var methodNameWithNoEnv = string.Format(CultureInfo.InvariantCulture, methodName, "");//方法是共有的静态的或非静态的方法var methods = startupType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);//查找包含环境变量的ConfigureServices方法名称var selectedMethods = methods.Where(method => method.Name.Equals(methodNameWithEnv, StringComparison.OrdinalIgnoreCase)).ToList();if (selectedMethods.Count > 1){//找打多个满足规则的方法直接抛出异常throw new InvalidOperationException(string.Format("Having multiple overloads of method '{0}' is not supported.", methodNameWithEnv));}//如果不存在包含环境变量的ConfigureServices的方法比如(ConfigureDevelopmentServices),则直接查找方法名为ConfigureServices的方法if (selectedMethods.Count == 0){selectedMethods = methods.Where(method => method.Name.Equals(methodNameWithNoEnv, StringComparison.OrdinalIgnoreCase)).ToList();//如果存在多个则同样抛出异常if (selectedMethods.Count > 1){throw new InvalidOperationException(string.Format("Having multiple overloads of method '{0}' is not supported.", methodNameWithNoEnv));}}var methodInfo = selectedMethods.FirstOrDefault();//如果没找到满足规则的方法,并且满足required参数,则抛出未找到方法的异常if (methodInfo == null){if (required){throw new InvalidOperationException(string.Format("A public method named '{0}' or '{1}' could not be found in the '{2}' type.",methodNameWithEnv,methodNameWithNoEnv,startupType.FullName));}return null;}//如果找到了名称一致的方法,但是返回类型和预期的不一致,也抛出异常if (returnType != null && methodInfo.ReturnType != returnType){if (required){throw new InvalidOperationException(string.Format("The '{0}' method in the type '{1}' must have a return type of '{2}'.",methodInfo.Name,startupType.FullName,returnType.Name));}return null;}return methodInfo;
}

通过FindMethod方法我们可以得到几个结论,首先ConfigureServices方法的名称可以是包含环境变量的名称比如(ConfigureDevelopmentServices),其次方法可以为共有的静态或非静态方法。FindMethod方法是真正执行查找的逻辑所在,如果找到相关方法则返回MethodInfo。FindMethod查找的方法名称是通过methodName参数传递进来的,我们标注的注释代码都是直接写死了ConfigureServices方法,只是为了便于说明理解,但其实FindMethod是通用方法,接下来我们要讲解的内容还会涉及到这个方法,到时候关于这个代码的逻辑我们就不会在进行说明了,因为是同一个方法,希望大家能注意到这一点。
通过上面的相关方法,我们了解到了是通过什么样的规则去查找到ConfigureServices的方法信息的,我们也看到了ConfigureServicesBuilder正是通过查找到的MethodInfo去构造实例的,接下来我们就来查看下ConfigureServicesBuilder的实现源码[点击查看源码????]

internal class ConfigureServicesBuilder
{//构造函数传递的configureServices的MethodInfopublic ConfigureServicesBuilder(MethodInfo configureServices){MethodInfo = configureServices;}public MethodInfo MethodInfo { get; }public Func<Func<IServiceCollection, IServiceProvider>, Func<IServiceCollection, IServiceProvider>> StartupServiceFilters { get; set; } = f => f;//Build委托public Func<IServiceCollection, IServiceProvider> Build(object instance) => services => Invoke(instance, services);private IServiceProvider Invoke(object instance, IServiceCollection services){//执行StartupServiceFilters委托参数为Func<IServiceCollection, IServiceProvider>类型的委托方法即Startup//返回了Func<IServiceCollection, IServiceProvider>委托,执行这个委托需传递services即IServiceCollections实例返回IServiceProvider类型return StartupServiceFilters(Startup)(services);IServiceProvider Startup(IServiceCollection serviceCollection) => InvokeCore(instance, serviceCollection);}private IServiceProvider InvokeCore(object instance, IServiceCollection services){if (MethodInfo == null){return null;}// 如果ConfigureServices方法包含多个参数或方法参数类型不是IServiceCollection类型则直接抛出异常// 也就是说ConfigureServices只能包含一个参数且类型为IServiceCollectionvar parameters = MethodInfo.GetParameters();if (parameters.Length > 1 ||parameters.Any(p => p.ParameterType != typeof(IServiceCollection))){throw new InvalidOperationException("The ConfigureServices method must either be parameterless or take only one parameter of type IServiceCollection.");}//找到ConfigureServices方法的参数,并将services即IServiceCollection的实例传递给这个参数var arguments = new object[MethodInfo.GetParameters().Length];if (parameters.Length > 0){arguments[0] = services;}// 执行返回IServiceProvider实例return MethodInfo.InvokeWithoutWrappingExceptions(instance, arguments) as IServiceProvider;}
}

看完ConfigureServicesBuilder类的实现逻辑,关于通过什么样的逻辑查找并执行ConfigureServices方法的逻辑就非常清晰了。首先是查找ConfigureServices方法,即包含环境变量的ConfigureServices方法名称比如(ConfigureDevelopmentServices)或名为ConfigureServices的方法,返回的是ConfigureServicesBuilder对象。然后执行ConfigureServicesBuilder的Build方法,这个方法里包含了执行ConfigureServices的规则,即ConfigureServices只能包含一个参数且类型为IServiceCollection,然后将当前程序中存在的IServiceCollection实例传递给它。

Configure的装载

我们常使用Startup的Configure方法去配置中间件,默认生成的Configure方法为我们添加了IApplicationBuilder和IWebHostEnvironment实例,但是其实Configure方法不仅仅可以传递这两个参数,它可以通过参数注入在IServiceCollection中注册的所有服务,究竟是如何实现的呢,接下来我们继续探究UseStartup方法查找源码查看想实现[点击查看源码????],我们抽离出来核心实现如下

//和ConfigureServices查找方式类似传递Startup实例和环境变量
ConfigureBuilder configureBuilder = StartupLoader.FindConfigureDelegate(startupType, context.HostingEnvironment.EnvironmentName);
services.Configure<GenericWebHostServiceOptions>(options =>
{//通过查看GenericWebHostServiceOptions的源码可知app其实就是IApplicationBuilder实例options.ConfigureApplication = app =>{startupError?.Throw();//执行Startup.Configure,instance为Startup实例if (instance != null && configureBuilder != null){  //执行Configure方法传递Startup实例和IApplicationBuilder实例configureBuilder.Build(instance)(app);}};
});

我们通过查看GenericWebHostServiceOptions的源码可知ConfigureApplication属性的类型为Action也就是说app参数其实就是IApplicationBuilder接口的实例。通过上面这段代码可以看出,主要逻辑就是调用StartupLoader的FindConfigureDelegate方法,然后返回ConfigureBuilder建造类,然后构建出Configure方法并执行。首先我们来查看FindConfigureDelegate的逻辑实现[点击查看源码????]

internal static ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName)
{//通过startup类型和方法名为Configure或Configure+环境变量名称的方法var configureMethod = FindMethod(startupType, "Configure{0}", environmentName, typeof(void), required: true);//用查找到的方法去初始化ConfigureBuilderreturn new ConfigureBuilder(configureMethod);
}

从这里我们可以看到FindConfigureDelegate方法也是调用的FindMethod方法,只是传递的方法名字符串为Configure或Configure+环境变量,关于FindMethod的方法实现我们在上面讲解ConfigureServices方法的时候已经非常详细的说过了,这里就不过多的讲解了。总之是通过FindMethod去查找名为Configure的方法或名为Configure+环境变量的方法比如ConfigureDevelopment查找规则和ConfigureServices是完全一致的。但是Configure方法却可以通过参数注入注册到IServiceCollection中的服务,答案我们同样要在ConfigureBuilder类中去探寻
[点击查看源码????]

internal class ConfigureBuilder
{//构造函数传递Configure的MethodInfopublic ConfigureBuilder(MethodInfo configure){MethodInfo = configure;}public MethodInfo MethodInfo { get; }//Build方法返回Action<IApplicationBuilder>委托public Action<IApplicationBuilder> Build(object instance) => builder => Invoke(instance, builder);//执行逻辑private void Invoke(object instance, IApplicationBuilder builder){//通过IApplicationBuilder的ApplicationServices获取IServiceProvider实例创建一个作用域using (var scope = builder.ApplicationServices.CreateScope()){//获取IServiceProvider实例var serviceProvider = scope.ServiceProvider;//获取Configure的所有参数var parameterInfos = MethodInfo.GetParameters();var parameters = new object[parameterInfos.Length];for (var index = 0; index < parameterInfos.Length; index++){var parameterInfo = parameterInfos[index];//如果方法参数为IApplicationBuilder类型则直接将传递过来的IApplicationBuilder赋值给它if (parameterInfo.ParameterType == typeof(IApplicationBuilder)){parameters[index] = builder;}else{try{//根据方法的参数类型在serviceProvider中获取具体实例赋值给对应参数parameters[index] = serviceProvider.GetRequiredService(parameterInfo.ParameterType);}catch (Exception ex){//如果对应的方法参数名称,没在serviceProvider中获取到则直接抛出异常//变相的说明了Configure方法的参数必须是注册在IServiceCollection中的}}}MethodInfo.InvokeWithoutWrappingExceptions(instance, parameters);}}
}

通过ConfigureBuilder类的实现逻辑,可以清晰的看到为何Configure方法参数可以注入任何在IServiceCollection中注册的服务了。接下来我们总结一下Configure方法的初始化逻辑,首先在Startup中查找方法名为Configure或Configure+环境变量名称(比如ConfigureDevelopment)的方法,然后查找IApplicationBuilder类型的参数,如果找到则将程序中的IApplicationBuilder实例传递给它。至于为何Configure方法能够通过参数注入任何在IServiceCollection中注册的服务,则是因为循环Configure中的所有参数然后在IOC容器中获取对应实例赋值过来,Configure方法的参数一定得是在IServiceCollection注册过的类型,否则会抛出异常。

ConfigureContainer为何会被调用

如果你在ASP.NET Core 3.1中使用过Autofac那么你对ConfigureContainer方法一定不陌生,它和ConfigureServices、Configure方法一样的神奇,在几乎没有任何约束的情况下我们只需要定义ConfigureContainer方法并为方法传递一个ContainerBuilder参数,那么这个方法就能顺利的被调用了。这一切究竟是如何实现的呢,接下来我们继续探究源码,找到了如下的逻辑[点击查看源码????]

//根据规则查找最终返回ConfigureContainerBuilder实例
var configureContainerBuilder = StartupLoader.FindConfigureContainerDelegate(startupType, context.HostingEnvironment.EnvironmentName);
if (configureContainerBuilder.MethodInfo != null)
{//获取容器类型比如如果是autofac则类型为ContainerBuildervar containerType = configureContainerBuilder.GetContainerType();// 存储configureContainerBuilder实例_builder.Properties[typeof(ConfigureContainerBuilder)] = configureContainerBuilder;//构建一个Action<HostBuilderContext,containerType>类型的委托var actionType = typeof(Action<,>).MakeGenericType(typeof(HostBuilderContext), containerType);// 获取此类型的私有ConfigureContainer方法,然后声明该方法的泛型为容器类型,然后创建这个方法的委托var configureCallback = GetType().GetMethod(nameof(ConfigureContainer), BindingFlags.NonPublic | BindingFlags.Instance).MakeGenericMethod(containerType).CreateDelegate(actionType, this);// 等同于执行_builder.ConfigureContainer<T>(ConfigureContainer),其中T为容器类型。//C onfigureContainer表示一个委托,即我们在Startup中定义的ConfigureContainer委托typeof(IHostBuilder).GetMethods().First(m => m.Name == nameof(IHostBuilder.ConfigureContainer)).MakeGenericMethod(containerType).InvokeWithoutWrappingExceptions(_builder, new object[] { configureCallback });
}

继续使用老配方,我们查看StartupLoader的FindConfigureContainerDelegate方法实现[点击查看源码????]

internal static ConfigureContainerBuilder FindConfigureContainerDelegate(Type startupType, string environmentName)
{//根据startupType和根据environmentName构建的Configure{0}Services字符串先去查找返回类型为IServiceProvider的方法var configureMethod = FindMethod(startupType, "Configure{0}Container", environmentName, typeof(void), required: false);//用查找到的方法去初始化ConfigureContainerBuilderreturn new ConfigureContainerBuilder(configureMethod);
}

果然还是这个配方这个味道,废话不多说直接查看ConfigureContainerBuilder源码[点击查看源码????]

internal class ConfigureContainerBuilder
{public ConfigureContainerBuilder(MethodInfo configureContainerMethod){MethodInfo = configureContainerMethod;}public MethodInfo MethodInfo { get; }public Func<Action<object>, Action<object>> ConfigureContainerFilters { get; set; } = f => f;public Action<object> Build(object instance) => container => Invoke(instance, container);//查找容器类型,其实就是ConfigureContainer方法的的唯一参数public Type GetContainerType(){var parameters = MethodInfo.GetParameters();//ConfigureContainer方法只能包含一个参数if (parameters.Length != 1){throw new InvalidOperationException($"The {MethodInfo.Name} method must take only one parameter.");}return parameters[0].ParameterType;}private void Invoke(object instance, object container){ConfigureContainerFilters(StartupConfigureContainer)(container);void StartupConfigureContainer(object containerBuilder) => InvokeCore(instance, containerBuilder);}//根据传递的container对象执行ConfigureContainer方法逻辑比如使用autofac时ConfigureContainer(ContainerBuilder)private void InvokeCore(object instance, object container){if (MethodInfo == null){return;}var arguments = new object[1] { container };MethodInfo.InvokeWithoutWrappingExceptions(instance, arguments);}
}

果不其然千年老方下来还是那个味道,和ConfigureServices、Configure方法思路几乎一致。这里需要注意的是GetContainerType获取的容器类型是ConfigureContainer方法的唯一参数即容器类型,如果传递多个参数则直接抛出异常。其实Startup的ConfigureContainer方法经过花里胡哨的一番操作之后,最终还是转换成了雷士如下的操作方式,这个我们在上面代码中构建actionType的时候就可以看出,最终通过查找到的容器类型去完成注册等相关操作,这里就不过多的讲解了

Host.CreateDefaultBuilder(args).ConfigureContainer<ContainerBuilder>((context,container)=> {container.RegisterType<PersonService>().As<IPersonService>().InstancePerLifetimeScope();});

总结

    本篇文章我们主要是围绕着Startup是如何被初始化进行讲解的,分别讲解了Startup是如何被实例化的,为何Startup的构造函数只能传递IWebHostEnvironment、IHostEnvironment、IConfiguration类型的参数,以及ConfigureServices、Configure、ConfigureContainer方法是如何查找到并被初始化调用的。其中虽然涉及到的代码比较多,但是整体思路在阅读源码后还是比较清晰的。由于笔者文笔有限,可能许多地方描述的不够清晰,亦或是本人能力有限理解的不够透彻,不过本人在文章中都标记了源码所在位置的链接,如果有感兴趣的同学可以自行点击连接查看源码。Startup类比较常用,如果能够更深层次的了解其原理,对我们实际编程过程中会有很大的帮助,同时呼吁更多的小伙伴们深入阅读了解.NET Core的源码并分享出来。如有各位有疑问或者有了解的更透彻的,欢迎评论区提问或批评指导。

????欢迎扫码关注我的公众号????

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307102.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【源码】常用的人脸识别数据库以及上篇性别识别源码

上一篇《使用ML.NET模型生成器来完成图片性别识别》发布后&#xff0c;很多朋友希望得到源码&#xff0c;这里附上地址&#xff1a;https://github.com/xin-lai/GenderRecognition常用的人脸数据库对于部分朋友说&#xff0c;找不到训练的数据&#xff0c;这里也给出部分数据&a…

程序员过关斩将--真的可以用版本号的方式来保证MQ消费消息的幂等性?

灵魂拷问MQ消息的消费为什么有时候要求幂等性&#xff1f;你们都说可以用版本号来解决幂等性消费&#xff1f;什么才是消息幂等性消费的根本性问题&#xff1f;随着系统的复杂性不断增加&#xff0c;多数系统都会引入MQ来进行解耦&#xff0c;其实从引入MQ的初衷来说&#xff0…

spring的钩子_spring提供的钩子,你知道哪些

俗话说得好“工欲善其事必先利其器”&#xff0c;现如今springboot与springcloud已成为快速构建web应用的利器。作为一个爪洼工程师&#xff0c;知道如下的spring扩展点&#xff0c;可能会让你编写出扩展性、维护性更高的代码。spring提供的钩子&#xff0c;你知道哪些bean的生…

.Net 5性能改进

起因在.Net Core跳过4.0,避免和先.Net Framework 4.0同名,版本号变为5.0,同时也不在叫.Net Core改为.Net 5(统一的叫法),先看看官方对.Net版本规划.本文主要是根据https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-5/ 翻译而来.不完全翻译.顺序也有所调…

开放数字世界中的复杂图数据挑战 —— 以教育与开源场景为例

摘要&#xff1a;开源开放的数字世界开始成为时代的潮流&#xff0c;云原生、数据中台、智能PRA开始成为数字世界中的新一代中流砥柱。随着第四范式的普遍流行&#xff0c;各个行业中的数字化转型都会带了海量的具有无限关联的复杂图数据。本报告将以教育与开源两个场景为例&am…

在IIS中部署SPA应用,多么痛的领悟!

目前公司的Web项目是SPA应用&#xff0c;采用前后端分离开发&#xff0c;所以有时也会倒腾Vue框架。“前后端应用最终以容器形态、在k8s中部署, 为此我搭建了基于Gitlab flow的Devops流程。在Devops实践中&#xff0c;容器部署成为良方和事实标准。但是在开发和自测阶段&#x…

mysql闪回工具下载_MySQL闪回工具之myflash 和 binlog2sql

实践利用binlog2sql查询两个binlog之间的SQL&#xff1a;必须是两个binlog日志&#xff0c;指定start-file和stop-filebinlog2sql -h127.0.0.1 -P3309 -udba -pxxxxxx -dsakila -t employee --start-filemysql-bin.000112 --stop-filemysql-bin.000113 > /tmp/db.sql利用bin…

MySQL大表优化方案

背景阿里云RDS FOR MySQL&#xff08;MySQL5.7版本&#xff09;数据库业务表每月新增数据量超过千万,随着数据量持续增加,我们业务出现大表慢查询,在业务高峰期主业务表的慢查询需要几十秒严重影响业务方案概述一、数据库设计及索引优化MySQL数据库本身高度灵活&#xff0c;造成…

使用Azure静态Web应用部署Blazor Webassembly应用

上一次演示了如何使用Azure静态web应用部署VUE前端项目&#xff08;使用Azure静态web应用全自动部署VUE站点&#xff09;。我们知道静态web应用支持VUE&#xff0c;react&#xff0c;angular等项目的部署。除了支持这些常见前端框架&#xff0c;静态web应用同样支持微软推出的最…

TIOBE 11 月榜单:Python 挤掉 Java,Java的下跌趋势确立了?

喜欢就关注我们吧&#xff01;TIOBE 公布了 2020 年 11 月的编程语言排行榜。Python 已成功跃居榜单第二名&#xff0c;本月排名率为 12.12%&#xff1b;Java 被挤到第三位&#xff0c;排名率降至 11.68%。自有 TIOBE 榜单以来&#xff0c;C 和 Java 之前一直占据着前两名的位置…

一路踩坑,被迫聊聊 C# 代码调试技巧和远程调试

一&#xff1a;背景 1. 讲故事每次项目预交付的时候&#xff0c;总会遇到各种奇葩的坑&#xff0c;我觉得有必要梳理一下以及如何快速解决的&#xff0c;让后来人避避坑&#xff0c;这篇就聊聊自己的所闻所遇&#xff1a;我去&#xff0c;本地环境代码跑的哧溜&#xff0c;上了…

mysql decimal型转化为float_5分钟搞懂MySQL数据类型之数值型DECIMAL类型

速成指南5分钟搞懂MySQL数据类型之数值型--DECIMAL类型DECIMAL类型的语法&#xff1a;DECIMAL[(M[,D])] [UNSIGNED] [ZEROFILL]。其中M指定的是数字的总位数(精度&#xff0c;最大65&#xff0c;默认值10)&#xff0c;D指定的是小数点后数字的位数(最大30&#xff0c;并且不能大…

Java面试必问JVM调优,那.NET5呢?

JVM调优已经是普通Java工程师的必修课了&#xff0c;而.NET开源快5年了&#xff0c;CLR层面的优化到目前都不多见&#xff0c;甚至常用的性能调优工具都还没玩过。.NET5马上来了&#xff0c;要想在互联网大潮中逆袭&#xff0c;光靠平台是不够的&#xff0c;开发者也得给力才行…

win10新建管理员账户_【经验篇001】Win10专业版如何开启超级管理员账户

关注我们前言介绍我们在使用Win10系统的时候&#xff0c;有时候安装一些特殊的专业类软件&#xff0c;需要系统赋予软件特殊的权限&#xff0c;那就需要使用超级管理员帐户&#xff0c;Win10系统安装时候&#xff0c;Administrator账户默认是禁用的&#xff0c;所以我们就需要开…

SQL Server in Docker - 还原数据库

SQL Server in Docker 还原数据库上一回演示了如果在Docker环境下安装SQL Server(使用Docker运行SQL Server)&#xff0c;这次我们来演示下如何还原一个数据库备份文件到数据库实例上。使用winscp上传bak文件到linux服务器上一回我们启动docker容器的时候使用了-v参数挂账了本地…

Xamarin 从零开始部署 iOS 上的 Walterlv.CloudKeyboard 应用

本文将告诉大家如何从零开始在 iOS 上部署 Walterlv.CloudKeyboard 应用。这个 Walterlv.CloudKeyboard 应用是一个云输入法应用&#xff0c;在 GitHub 完全开源&#xff0c;采用 Xamarin 开发&#xff0c;用途是让手机接收电脑端的打字输入的输入法。因为我没有在 iOS 上找到任…

Win10 Terminal + WSL 2 安装配置指南,精致开发体验

自从 Windows Terminal 正式发布后就再没有用过 Windows 系统自带的终端了。主要是 Terminal 简洁且灵活&#xff0c;更重要的是支持特殊字体&#xff0c;通过一些简单的配置可以使得终端看起来更舒适养眼。自从 Win 10 有了 Linux 子系统&#xff08;WSL&#xff09;&#xff…

mysql数据转储方法_Mysql数据库各种导出导入数据方式的区别(我的理解错误还望指正)...

mysqldump,NAVICAT转储&#xff0c;select * outfile在千级数据&#xff0c;万级&#xff0c;百万级数据下的表现。千级数据mysqldump导出sql文件导出是出了拒绝访问的错误&#xff1b;为对应目录(.sql文件要保存的目录)的对应用户添加(正在使用的用户)添加写入权限即可。mysql…

JetBrains 开发者调查 - 编程语言趋势

几个月前在公众号里发布了 StackOverflow 2020 开发者调查结果&#xff0c;其结果对 .NET Core 很友好。今天我们看看 JetBrains 2017-2020 四年的开发者调查结果统计&#xff0c;JetBrains 是偏 Java 系的&#xff0c;尤其是本家的 Kotlin 语言。 我们看一下在编程语言方面的趋…

mycli mysql_MyCLI :易于使用的 MySQL/MariaDB 客户端

导读MyCLI 是一个易于使用的命令行客户端&#xff0c;可用于受欢迎的数据库管理系统 MySQL、MariaDB 和 Percona&#xff0c;支持自动补全和语法高亮。它是使用 prompt_toolkit库写的&#xff0c;需要 Python 2.7、3.3、3.4、3.5 和 3.6 的支持。MyCLI 还支持通过 SSL 安全连接…