深入探究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 { //根据你配置的程序集去查找Startup var 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; }

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

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