应用启动的重要类 - Startup

  在ASP.NET Core - 从Program和Startup开始这篇文章里面,我们知道了Startup这个类的重要性,它主要负责了:

  1. 配置应用需要的服务(服务注册,ConfigureServices方法)。
  2. 创建应用的请求处理处理管道(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 (ex is 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)
{
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<MethodInfo> list = ((IEnumerable<MethodInfo>) methods).Where<MethodInfo>((Func<MethodInfo, bool>) (method => method.Name.Equals(methodNameWithEnv, StringComparison.OrdinalIgnoreCase))).ToList<MethodInfo>();
if (list.Count > )
throw new InvalidOperationException(string.Format("Having multiple overloads of method '{0}' is not supported.", (object) methodNameWithEnv));
if (list.Count == )
{
list = ((IEnumerable<MethodInfo>) methods).Where<MethodInfo>((Func<MethodInfo, bool>) (method => method.Name.Equals(methodNameWithNoEnv, StringComparison.OrdinalIgnoreCase))).ToList<MethodInfo>();
if (list.Count > )
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实现通用注册】这篇文章中提到,我们基于返回的IServiceProvider进行容器替换进而实现通用注册,对于没有返回值的情况,系统会根据当前注册的服务创建一个ServiceProvider。

  真正的注册

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

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

  总结

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

ASP.NET Core[源码分析篇] - Startup的更多相关文章

  1. ASP.NET Core[源码分析篇] - WebHost

    _configureServicesDelegates的承接 在[ASP.NET Core[源码分析篇] - Startup]这篇文章中,我们得知了目前为止(UseStartup),所有的动作都是在_ ...

  2. ASP.NET Core[源码分析篇] - Authentication认证

    原文:ASP.NET Core[源码分析篇] - Authentication认证 追本溯源,从使用开始 首先看一下我们通常是如何使用微软自带的认证,一般在Startup里面配置我们所需的依赖认证服务 ...

  3. ASP.NET Core[源码分析篇] - 认证

    追本溯源,从使用开始 首先看一下我们的通常是如何使用微软自带的认证,一般在Startup里面配置我们所需的依赖认证服务,这里通过JWT的认证方式讲解 public void ConfigureServ ...

  4. 一个由正则表达式引发的血案 vs2017使用rdlc实现批量打印 vs2017使用rdlc [asp.net core 源码分析] 01 - Session SignalR sql for xml path用法 MemCahe C# 操作Excel图形——绘制、读取、隐藏、删除图形 IOC,DIP,DI,IoC容器

    1. 血案由来 近期我在为Lazada卖家中心做一个自助注册的项目,其中的shop name校验规则较为复杂,要求:1. 英文字母大小写2. 数字3. 越南文4. 一些特殊字符,如“&”,“- ...

  5. [asp.net core 源码分析] 01 - Session

    1.Session文档介绍 毋庸置疑学习.Net core最好的方法之一就是学习微软.Net core的官方文档:https://docs.microsoft.com/zh-cn/aspnet/cor ...

  6. ASP.NET Core源码学习(一)Hosting

    ASP.NET Core源码的学习,我们从Hosting开始, Hosting的GitHub地址为:https://github.com/aspnet/Hosting.git 朋友们可以从以上链接克隆 ...

  7. ASP.NET MVC 源码分析(一)

    ASP.NET MVC 源码分析(一) 直接上图: 我们先来看Core的设计: 从项目结构来看,asp.net.mvc.core有以下目录: ActionConstraints:action限制相关 ...

  8. DOTNET CORE源码分析之IOC容器结果获取内容补充

    补充一下ServiceProvider的内容 可能上一篇文章DOTNET CORE源码分析之IServiceProvider.ServiceProvider.IServiceProviderEngin ...

  9. ASP.NET MVC源码分析

    MVC4 源码分析(Visual studio 2012/2013) HttpModule中重要的UrlRoutingModule 9:this.OnApplicationPostResolveReq ...

随机推荐

  1. 在asp.net 中web.config配置错误页

    每当用户访问错误页面时,会出现不友好的错误页面,所以为了防止这种不友好,我们在web.config中的<system.web>节点下配置 <customErrors>,在出现比 ...

  2. Docker笔记03-docker 网络模式

    docker网络模式分为5种 Nat (Network Address Translation) Host other container none overlay 第一种 Nat模式 docker的 ...

  3. Delphi编程中Http协议应用

    Http协议的通信遵循一定的约定.例如,请求一个文件的时候先发送Get请求,然后服务器会返回请求的数据.如果需要进行断点传输,那么先发送'HEAD /'请求,其中返回的'Content-Length: ...

  4. 什么是Android NDK

    1.NDK是一系列工具的集合. NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk.这些工具对开发者的帮助是巨大的. NDK集成了交叉编译 ...

  5. 配置我的Ubuntu Server记(包括桌面及VNC,SSH,NTP,NFS服务) good

    跟老板申请买了一台配置相对较好的计算机回来做GPU计算,当然,不能独享,所以做成服务器让大家都来用. 这篇日志用来记录配置过程中遇到的一些问题,以方便下次不需要到处谷歌度娘. 安装Server版系统 ...

  6. Tido 习题-二叉树-区间查询

    题目描述 食堂有N个打饭窗口,现在正到了午饭时间,每个窗口都排了很多的学生,而且每个窗口排队的人数在不断的变化.现在问你第i个窗口到第j个窗口一共有多少人在排队? 输入 输入的第一行是一个整数T,表示 ...

  7. spring 5.x 系列第20篇 ——spring简单邮件、附件邮件、内嵌资源邮件、模板邮件发送 (代码配置方式)

    源码Gitub地址:https://github.com/heibaiying/spring-samples-for-all 一.说明 1.1 项目结构说明 邮件发送配置类为com.heibaiyin ...

  8. Java NIO 学习笔记(二)----聚集和分散,通道到通道

    目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...

  9. vs 编译说明

    静态编译/MT,/MTD 是指使用libc和msvc相关的静态库(lib).   动态编译,/MD,/MDd是指用相应的DLL版本编译.   其中字母含义  d:debug    m:multi-th ...

  10. LBXF时间管理法2.0

    前言 LBXF是柳比歇夫的中文拼音缩写,下文中会着重介绍柳比歇夫 时间管理法主要帮助我们提高生产性时间,以及提高生产效率.我会按照柳比歇夫.执行步骤.时间管理工具Toggle三个模块来讲解LBXF时间 ...