ASP.NET Core[ソース分析編]-Startup
29839 ワード
アプリケーション起動の重要クラス-Startup
ASP.NET Core-ProgramとStartupからこの文章では、Startupというクラスの重要性を知っています.主に担当しています.
ソース分析の前に補足します.私たちは一般的に約束通りにこのクラス名をStartupと定義していますが、実際のアプリケーションでは、Startupと命名する必要はありません.これは抽象的な概念です.他のクラス名を命名することができます.UseStartup/UseStartupにこの起動クラスを明示的に登録するだけでいいです.システムはこの起動クラスを単例に登録します.例えば、次のようにします.
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クラスを登録したのかを見てみましょう.
///
Specify the startup type to be used by the web host. /// The
to configure. /// The to be used. /// The . public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder,Type startupType) { string name = startupType.GetTypeInfo().Assembly.GetName().Name; return hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, name).ConfigureServices((Action ) (services => { if (typeof (IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo())) ServiceCollectionServiceExtensions.AddSingleton(services, typeof (IStartup), startupType); else ServiceCollectionServiceExtensions.AddSingleton(services, typeof (IStartup), (Func object>) (sp => { IHostingEnvironment requiredService = sp.GetRequiredService (); return (object) new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, requiredService.EnvironmentName)); })); })); } /// Specify the startup type to be used by the web host. /// The
to configure. /// The type containing the startup methods for the application. /// The . public static IWebHostBuilder UseStartup (this IWebHostBuilder hostBuilder) where TStartup : class { return hostBuilder.UseStartup(typeof (TStartup)); }
_configureServicesDelegates
从上面代码我们可以看出,这里主要是调用了WebHostBuilder的ConfigureServices方法,我们看一下ConfigureServices做了什么
public IWebHostBuilder ConfigureServices( Action
configureServices) { if (configureServices == null) throw new ArgumentNullException(nameof (configureServices)); return this.ConfigureServices((Action ) ((_, services) => configureServices(services))); } /// /// Adds a delegate for configuring additional services for the host or web application. This may be called /// multiple times. /// /// A delegate for configuring the
. /// The . public IWebHostBuilder ConfigureServices( Action 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 (ex is TargetInvocationException)
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
throw;
}
}
}
このConventionBasedStartupはIStartupインタフェースも実装されていることに注意してください.ConventionBasedStartupオブジェクトはStartupMethodsオブジェクトに基づいて作成されています.このStartupMethodsタイプの定義を見てみましょう.
public class StartupMethods
{
public StartupMethods(object instance,Action configure, Func configureServices)
{
this.StartupInstance = instance;
this.ConfigureDelegate = configure;
this.ConfigureServicesDelegate = configureServices;
}
public object StartupInstance { get; }
public Func ConfigureServicesDelegate { get; }
public Action ConfigureDelegate { get; }
}
StartupMethodsでは、2つの登録サービスとミドルウェアの方法しか提供されていません.この2つの方法は、その2つのプロパティ(C o n f i g u reServicesDelegateとConfigureDelegate)によって提供される2つの依頼オブジェクトに反映されます.
私たちのUseStartupコードでは、Startup Loaderを通じてLoadMethods Startupタイプに基づいてStartupMethodsを取得
public class StartupLoader
{
public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider,Type startupType,string environmentName)
{
ConfigureBuilder configureDelegate = 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 ConfigureBuilder FindConfigureDelegate(Type startupType, string environmentName)
{
return new ConfigureBuilder(StartupLoader.FindMethod(startupType, "Configure{0}", environmentName, typeof (void), true));
}
private static ConfigureContainerBuilder FindConfigureContainerDelegate(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 list = ((IEnumerable) methods).Where((Funcbool>) (method => method.Name.Equals(methodNameWithEnv, StringComparison.OrdinalIgnoreCase))).ToList();
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) methods).Where((Funcbool>) (method => method.Name.Equals(methodNameWithNoEnv, StringComparison.OrdinalIgnoreCase))).ToList();
if (list.Count > 1)
throw new InvalidOperationException(string.Format("Having multiple overloads of method '{0}' is not supported.", (object) methodNameWithNoEnv));
}
MethodInfo methodInfo = list.FirstOrDefault();
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クラスのこの2つの方法は、ConfigureServicesとConfigureと命名できるほか、実行環境名を携帯することもできます.具体的なフォーマットは、Configure{EnvironmentName}ServicesとConfigure{EnvironmentName}であり、後者はより高い選択優先度を持っています.
FindConfigureServicesDelegateという方法の実装に注意してください.一般的に、ConfigureServices/Configure{EnvironmentName}Servicesという方法では戻り値(戻りタイプはvoid)はありませんが、戻りタイプがIServiceProviderである方法として定義することもできます.このメソッドがServiceProviderオブジェクトを返すと、後続のプロセスで取得したすべてのサービスがこのServiceProviderから抽出されます.この返却されたServiceProviderは、「ASP.NET Core-Windsor Castleによる汎用登録」という記事で、返却されたIServiceProviderに基づいてコンテナを置き換えて汎用登録を実現し、返却値がない場合、現在登録されているサービスに基づいてServiceProviderを作成すると説明しています.
本格的な登録
ここまで、プログラムは実際の登録を実行していません.私たちはただ_configureServicesDelegatesは依頼を追加しただけで、実行していません.この実行はWebHostが本当にBuildメソッドを呼び出したときです.
実はCreateDefaultBuilderメソッドの他のいくつかのUseXXX(UserUrl,UseKestrelなど)拡張メソッドも同様であり,登録が必要なAction依頼に対応してconfigureServicesDelegatesに同様に書き込まれている.
まとめ
上のこの過程では、実際には多くの隠れた論理が含まれていることを見ることができます.これらの論理の理解は、後の本当の開発で自分のプロジェクトの実際の状況に基づいて選択できる選択肢を提供することができます.これまで、私たちが今見てきた最も重要な点は:configureServicesDelegatesのベアラ作用.