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

简介: ASP.NET Core[源码分析篇] - Startup 应用启动的重要类 - Startup  在ASP.NET Core - 从Program和Startup开始这篇文章里面,我们知道了Startup这个类的重要性,它主要负责了:配置应用需要的服务(服务注册,ConfigureServices方法)。

ASP.NET Core[源码分析篇] - Startup
 应用启动的重要类 - Startup
  在ASP.NET Core - 从Program和Startup开始这篇文章里面,我们知道了Startup这个类的重要性,它主要负责了:

配置应用需要的服务(服务注册,ConfigureServices方法)。
创建应用的请求处理处理管道(Configure方法)。  
  在源码分析之前补充一点,虽然我们一般是按约定把这个类名定义成了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.

/// <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 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,这里调用了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 > 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实现通用注册】这篇文章中提到,我们基于返回的IServiceProvider进行容器替换进而实现通用注册,对于没有返回值的情况,系统会根据当前注册的服务创建一个ServiceProvider。

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

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

  总结
  上面的这个过程,我们看到其实包含很多隐含的逻辑,这些逻辑的理解,可以提供多种选项让我们在后面的真正开发中能够根据自己项目的一个实际情况进行选择,到此为止,我们现在能看到的最重要的一个点是:_configureServicesDelegates的一个承载作用。
原文地址https://www.cnblogs.com/lex-wu/p/10782813.html

相关文章
|
13天前
|
开发框架 NoSQL .NET
利用分布式锁在ASP.NET Core中实现防抖
【9月更文挑战第5天】在 ASP.NET Core 中,可通过分布式锁实现防抖功能,仅处理连续相同请求中的首个请求,其余请求返回 204 No Content,直至锁释放。具体步骤包括:安装分布式锁库如 `StackExchange.Redis`;创建分布式锁服务接口及其实现;构建防抖中间件;并在 `Startup.cs` 中注册相关服务和中间件。这一机制有效避免了短时间内重复操作的问题。
|
23天前
|
开发框架 监控 .NET
开发者的革新利器:ASP.NET Core实战指南,构建未来Web应用的高效之道
【8月更文挑战第28天】本文探讨了如何利用ASP.NET Core构建高效、可扩展的Web应用。ASP.NET Core是一个开源、跨平台的框架,具有依赖注入、配置管理等特性。文章详细介绍了项目结构规划、依赖注入配置、中间件使用及性能优化方法,并讨论了安全性、可扩展性以及容器化的重要性。通过这些技术要点,开发者能够快速构建出符合现代Web应用需求的应用程序。
31 0
|
23天前
|
缓存 数据库连接 API
Entity Framework Core——.NET 领域的 ORM 利器,深度剖析其最佳实践之路
【8月更文挑战第28天】在软件开发领域,高效的数据访问与管理至关重要。Entity Framework Core(EF Core)作为一款强大的对象关系映射(ORM)工具,在 .NET 开发中扮演着重要角色。本文通过在线书店应用案例,展示了 EF Core 的核心特性和优势。我们定义了 `Book` 实体类及其属性,并通过 `BookStoreContext` 数据库上下文配置了数据库连接。EF Core 提供了简洁的 API,支持数据的查询、插入、更新和删除操作。
38 0
|
26天前
|
开发框架 监控 .NET
【Azure 应用程序见解】在Docker中运行的ASP.NET Core应用如何开启Application Insights的Profiler Trace呢?
【Azure 应用程序见解】在Docker中运行的ASP.NET Core应用如何开启Application Insights的Profiler Trace呢?
|
27天前
|
Linux C# C++
【Azure App Service For Container】创建ASP.NET Core Blazor项目并打包为Linux镜像发布到Azure应用服务
【Azure App Service For Container】创建ASP.NET Core Blazor项目并打包为Linux镜像发布到Azure应用服务
|
13天前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
20 7
|
11天前
|
存储 开发框架 前端开发
ASP.NET MVC 迅速集成 SignalR
ASP.NET MVC 迅速集成 SignalR
28 0
|
1月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
31 0
|
1月前
|
开发框架 前端开发 安全
ASP.NET MVC 如何使用 Form Authentication?
ASP.NET MVC 如何使用 Form Authentication?
|
1月前
|
开发框架 .NET
Asp.Net Core 使用X.PagedList.Mvc.Core分页 & 搜索
Asp.Net Core 使用X.PagedList.Mvc.Core分页 & 搜索
79 0