应用启动的重要类 - Startup

  在ASP.NET Core - 从Program和Startup开始
<https://www.cnblogs.com/lex-wu/p/10693437.html>
这篇文章里面,我们知道了Startup这个类的重要性,它主要负责了:

* 配置应用需要的服务(服务注册,ConfigureServices方法)。
* 创建应用的请求处理处理管道(Configure方法)。  

  在源码分析之前补充一点,虽然我们一般是按约定把这个类名定义成了Startup,但是在真正应用中,我们不是必须要命名为Startup的,这只是一个抽象概念,我们可以命名其他的类名,只需要在UseStartup/UseStartup<TStartup>中显式注册这个启动类即可,
系统会把这个启动类注册为单例,例如:  
public class Program { public static void Main(string[] args) {
BuildWebHost(args).Run(); }public static IWebHost BuildWebHost(string[] args) =>
WebHost.CreateDefaultBuilder(args) .UseStartup<YourStartupClass>() .Build(); }
public class YourStartupClass { public void ConfigureService(IServiceCollection
services) { }public void Configure(IApplicationBuilder app) { } }
  Startup是如何被注册进来的?

   从前面我们可以看到Startup是在UseStartup方法里面被引用进来,我们先看一下UseStartup是如何把Startup类注册进来的  
/// <summary>Specify the startup type to be used by the web host.</summary> ///
<param name="hostBuilder">The <see
cref="T:Microsoft.AspNetCore.Hosting.IWebHostBuilder" /> to configure.</param>
/// <param name="startupType">The <see cref="T:System.Type" /> to be used.
</param> /// <returns>The <see
cref="T:Microsoft.AspNetCore.Hosting.IWebHostBuilder" />.</returns> public
static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder,Type
startupType) {string name = startupType.GetTypeInfo().Assembly.GetName().Name;
return hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, name).
ConfigureServices((Action<IServiceCollection>) (services => { if (typeof
(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
ServiceCollectionServiceExtensions.AddSingleton(services,typeof (IStartup),
startupType);else ServiceCollectionServiceExtensions.AddSingleton(services,
typeof (IStartup), (Func<IServiceProvider, object>) (sp => {
IHostingEnvironment requiredService= sp.GetRequiredService<IHostingEnvironment>
();return (object) new ConventionBasedStartup(StartupLoader.LoadMethods(sp,
startupType, requiredService.EnvironmentName)); })); })); }/// <summary>Specify
the startup type to be used by the web host.</summary> /// <param
name="hostBuilder">The <see
cref="T:Microsoft.AspNetCore.Hosting.IWebHostBuilder" /> to configure.</param>
/// <typeparam name="TStartup">The type containing the startup methods for the
application.</typeparam> /// <returns>The <see
cref="T:Microsoft.AspNetCore.Hosting.IWebHostBuilder" />.</returns> public
static IWebHostBuilder UseStartup<TStartup>(this IWebHostBuilder hostBuilder)
where TStartup : class { return hostBuilder.UseStartup(typeof (TStartup)); }
  _configureServicesDelegates  


  从上面代码我们可以看出,这里主要是调用了WebHostBuilder的ConfigureServices方法,我们看一下ConfigureServices做了什么
public IWebHostBuilder ConfigureServices( Action<IServiceCollection>
configureServices) {if (configureServices == null) throw new
ArgumentNullException(nameof (configureServices));return this.ConfigureServices
((Action<WebHostBuilderContext, IServiceCollection>) ((_, services) =>
configureServices(services))); }/// <summary> /// Adds a delegate for
configuring additional services for the host or web application. This may be
called/// multiple times. /// </summary> /// <param name="configureServices">A
delegate for configuring the<see
cref="T:Microsoft.Extensions.DependencyInjection.IServiceCollection" />.</param>
/// <returns>The <see cref="T:Microsoft.AspNetCore.Hosting.IWebHostBuilder" />.
</returns> public IWebHostBuilder ConfigureServices( Action
<WebHostBuilderContext, IServiceCollection> configureServices) { if
(configureServices ==null) throw new ArgumentNullException(nameof
(configureServices));this._configureServicesDelegates.Add(configureServices);
return (IWebHostBuilder) this; }
  这里主要是把委托添加到_configureServicesDelegates列表里面,这个
_configureServicesDelegates有什么用呢?这个属性是一个非常重要的承载角色,在后面的WebHost真正调用Build方法时,我们再详细讲解。
  

  再次看回UseStartup,这里调用了WebHostBuilder的ConfigureServices并向
_configureServicesDelegates注册了一个委托,我们看一下这个委托的实体,这里面有两个分支:

  1. Startup实现IStartup接口


  直接注册该Startup为单例。从这里看出,其实我们的Startup类还有另一种方式实现的,就是直接实现IStartup接口。(其实还有一种是继承StartupBase)

  2. Startup没实现IStartup接口

  注册类型为ConventionBasedStartup的Startup类型
public class ConventionBasedStartup : IStartup { private readonly
StartupMethods _methods;public ConventionBasedStartup(StartupMethods methods) {
this._methods = methods; } public void Configure(IApplicationBuilder app) { try
{this._methods.ConfigureDelegate(app); } catch (Exception ex) { if (ex is
TargetInvocationException)
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();throw; } } public
IServiceProvider ConfigureServices(IServiceCollection services) {try { return
this._methods.ConfigureServicesDelegate(services); } catch (Exception ex) { if
(exis TargetInvocationException)
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();throw; } } }  
  注意这个ConventionBasedStartup
其实也是实现了IStartup接口,ConventionBasedStartup对象是根据一个StartupMethods对象创建的,我们来看一下这个StartupMethods类型的定义
public class StartupMethods { public StartupMethods(object
instance,Action<IApplicationBuilder> configure, Func<IServiceCollection,
IServiceProvider> configureServices) { this.StartupInstance = instance; this
.ConfigureDelegate = configure; this.ConfigureServicesDelegate =
configureServices; }public object StartupInstance { get; } public
Func<IServiceCollection, IServiceProvider>ConfigureServicesDelegate { get; }
public Action<IApplicationBuilder> ConfigureDelegate { get; } }

  StartupMethods只提供两个注册服务和中间件的方法,这两个方法体现在由它的两个属性(ConfigureServicesDelegate和ConfigureDelegate)提供的两个委托对象。

  在我们UseStartup代码里面,是通过StartupLoader.LoadMethods基于Startup类型获取到一个StartupMethods
public class StartupLoader { public static StartupMethods LoadMethods
(IServiceProvider hostingServiceProvider,Type startupType,string
environmentName) { ConfigureBuilderconfigureDelegate = StartupLoader.
FindConfigureDelegate(startupType, environmentName); ConfigureServicesBuilder
servicesDelegate = StartupLoader.FindConfigureServicesDelegate(startupType,
environmentName); ConfigureContainerBuilder containerDelegate= StartupLoader.
FindConfigureContainerDelegate(startupType, environmentName); object instance1
= (object) null; if (!configureDelegate.MethodInfo.IsStatic || servicesDelegate
!=null && !servicesDelegate.MethodInfo.IsStatic) instance1 =
ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider,
startupType); StartupLoader.ConfigureServicesDelegateBuilder instance2=
(StartupLoader.ConfigureServicesDelegateBuilder) Activator.CreateInstance(typeof

(StartupLoader.ConfigureServicesDelegateBuilder<>).MakeGenericType(containerDelegate.MethodInfo
!= (MethodInfo)null ? containerDelegate.GetContainerType() : typeof (object)), (
object) hostingServiceProvider, (object) servicesDelegate, (object)
containerDelegate, instance1);return new StartupMethods(instance1,
configureDelegate.Build(instance1), instance2.Build()); } private static
ConfigureBuilderFindConfigureDelegate(Type startupType, string environmentName)
{return new ConfigureBuilder(StartupLoader.FindMethod(startupType, "Configure{0}
", environmentName, typeof (void), true)); } private static
ConfigureContainerBuilderFindConfigureContainerDelegate(Type startupType, string
environmentName) {return new ConfigureContainerBuilder(StartupLoader.FindMethod
(startupType,"Configure{0}Container", environmentName, typeof (void), false)); }
private static ConfigureServicesBuilder FindConfigureServicesDelegate( Type
startupType,string environmentName) { MethodInfo method =
StartupLoader.FindMethod(startupType,"Configure{0}Services", environmentName,
typeof (IServiceProvider), false); if ((object) method == null) method =
StartupLoader.FindMethod(startupType, "Configure{0}Services", environmentName,
typeof (void), false); return new ConfigureServicesBuilder(method); } private
static MethodInfo FindMethod( Type startupType,string methodName,string
environmentName,Type returnType= null, bool required = true) { string
methodNameWithEnv =string.Format((IFormatProvider)
CultureInfo.InvariantCulture, methodName, (object) environmentName); string
methodNameWithNoEnv =string.Format((IFormatProvider)
CultureInfo.InvariantCulture, methodName, (object) ""); MethodInfo[] methods =
startupType.GetMethods(BindingFlags.Instance | BindingFlags.Static |
BindingFlags.Public); List<MethodInfo> list = ((IEnumerable<MethodInfo>)
methods).Where<MethodInfo>((Func<MethodInfo,bool>) (method =>
method.Name.Equals(methodNameWithEnv,
StringComparison.OrdinalIgnoreCase))).ToList<MethodInfo>(); if (list.Count > 1)
throw new InvalidOperationException(string.Format("Having multiple overloads of
method '{0}' is not supported.", (object) methodNameWithEnv)); if (list.Count ==
0) { list = ((IEnumerable<MethodInfo>)
methods).Where<MethodInfo>((Func<MethodInfo,bool>) (method =>
method.Name.Equals(methodNameWithNoEnv,
StringComparison.OrdinalIgnoreCase))).ToList<MethodInfo>(); if (list.Count > 1)
throw new InvalidOperationException(string.Format("Having multiple overloads of
method '{0}' is not supported.", (object) methodNameWithNoEnv)); } MethodInfo
methodInfo= list.FirstOrDefault<MethodInfo>(); if (methodInfo == (MethodInfo)
null) { if (required) throw new InvalidOperationException(string.Format("A
public method named '{0}' or '{1}' could not be found in the '{2}' type.", (
object) methodNameWithEnv, (object) methodNameWithNoEnv, (object)
startupType.FullName));return (MethodInfo) null; } if (!(returnType != (Type)
null) || !(methodInfo.ReturnType != returnType)) return methodInfo; if
(required)throw new InvalidOperationException(string.Format("The '{0}' method
in the type '{1}' must have a return type of '{2}'.", (object) methodInfo.Name,
(object) startupType.FullName, (object) returnType.Name)); return (MethodInfo)
null; } }

   这里面主要是通过反射获取startupType里面的Configure和ConfigureServices作为参数赋值给StartupMethods的ConfigureDelegate和ConfigureServicesDelegate,这样一个完整的
ConventionBasedStartup类型的startup就被注册为单例了。

  优先级


  比较有意思的是,我们可以看到,Startup类中的这两个方法除了可以命名为ConfigureServices和Configure之外,它们还可以携带运行环境名称,具体采用的格式分别为Configure{
EnvironmentName}Services和Configure{EnvironmentName},后者具有更高的选择优先级。

  注意到FindConfigureServicesDelegate
这个方法的实现,一般来说,ConfigureServices/Configure{EnvironmentName}Services这个方法不具有返回值(返回类型为void),但是它也可以定义成一个返回类型为
IServiceProvider
的方法。如果这个方法返回一个ServiceProvider对象,后续过程中获取的所有服务将从这个ServiceProvider中提取。这个返回的ServiceProvider对我们后续的一个注册是非常有用的,具体在
【ASP.NET Core - 利用Windsor Castle实现通用注册】
<https://www.cnblogs.com/lex-wu/p/10604767.html>
这篇文章中提到,我们基于返回的IServiceProvider进行容器替换进而实现通用注册,对于没有返回值的情况,系统会根据当前注册的服务创建一个ServiceProvider。

  注意到此为止,程序还是没有执行做一个真正的注册,因为我们只是往_configureServicesDelegates
添加了委托而已,并没有执行,这个的执行是在WebHost真正调用Build方法时。


  其实Create­DefaultBuilder方法中的其他几个UseXXX(UserUrl,UseKestrel等)扩展方法也是同样的道理,把对应需要注册的Action委托同样写入了configureServicesDelegates

  总结


  上面的这个过程,我们看到其实包含很多隐含的逻辑,这些逻辑的理解,可以提供多种选项让我们在后面的真正开发中能够根据自己项目的一个实际情况进行选择,到此为止,我们现在能看到的最重要的一个点是:
_configureServicesDelegates的一个承载作用。

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:ixiaoyang8@qq.com
QQ群:637538335
关注微信