ASP.NET Core [Source Analysis Section] - Startup

  Application start of an important class - Startup

  In ASP.NET Core - and Startup Program from the beginning of this article inside, we know the importance of Startup of this class, which is mainly responsible for:

  1. Service (service registration, ConfigureServices method) configuration application needs.
  2. Pipe creation request processing application (Configure method).  

  Prior to supplement source code analysis that, although we are generally agreed to by the class name became Startup defined, but in real applications, we do not have terribly named Startup, and this is just an abstract concept, we can name the other class names , only in this explicit registration UseStartup / UseStartup <tStartup> to start the class, the system will start the class is registered as a single embodiment , for example:  

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)  
    {
    }    
}

  How is Startup registered come in?

   From the front we can see that Startup is referenced to come inside UseStartup approach, we look at how the Startup UseStartup incoming class registration  

 /// <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  

  We can see from the above code, here is mainly called WebHostBuilder of ConfigureServices method, we look at what has been done 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;
    }

  This is mainly to add a delegate to _configureServicesDelegates list inside, this _configureServicesDelegates what use is it? This property is a very important bearing role, in the back of WebHost actually call the Build method, we then explain in detail.  

  Again look back UseStartup, here called ConfigureServices and _configureServicesDelegates registered a commission, we look at the delegate's entity, and there are two branches:

  1. Startup Interface to achieve IStartup

  The direct register Startup single embodiment. From here we see that, in fact, our Startup class has achieved another way is to directly implement IStartup interface. (In fact, there is a realization StartupBase)

  2. Startup did not realize IStartup Interface

  Registration type ConventionBasedStartup the Startup type

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;
      }
    }
  }  

  Note that this ConventionBasedStartup actually achieved IStartup interfaces, ConventionBasedStartup object is created based on a StartupMethods object, we look at the definition of this type 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 only provides two methods of registration services and middleware, these two methods in two delegate objects provided by two of its properties (ConfigureServicesDelegate and ConfigureDelegate).

  In our UseStartup code inside, it is to get a StartupMethods based Startup type by StartupLoader.LoadMethods

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;
    }
    
}

   This is mainly inside startupType acquired inside by reflecting ConfigureServices Configure and assigned to the ConfigureDelegate StartupMethods and ConfigureServicesDelegate as parameters, such a complete ConventionBasedStartup type of startup can be registered as a singleton.

  priority

  More interesting is that we can see, the Startup these two classes can be named as a method other than the Configure and ConfigureServices, they can also carry the name of the operating environment, specific formats are employed {Configure EnvironmentName } {Services and Configure EnvironmentName }, the latter having a higher selection priority.

  Noting FindConfigureServicesDelegate implement this method, in general, ConfigureServices / Configure {EnvironmentName} Services This method does not have a return value (void return type), but it can also be defined as a type of return IServiceProvider approach. If this method returns a ServiceProvider object, all acquired during the follow-up service from ServiceProvider extract this. This ServiceProvider us a registered subsequent return is very useful, in particular [ASP.NET Core - use Windsor Castle achieve universal registration] mentioned in this article, we replace container-based IServiceProvider returned thus achieving universal registration, in the case of no return value, the system creates a ServiceProvider based on the current registration service.

  Note that so far, the program did not do a real execution registered, because we just go _configureServicesDelegates add a commission only, and is not performed, this implementation is in WebHost true calling when Build method.

  In fact CreateDefaultBuilder method in several other UseXXX (UserUrl, UseKestrel, etc.) extension method is the same reason, the need to register the corresponding Action delegate is also written configureServicesDelegates

  to sum up

  Above this process, we see that in fact contains many implicit logic that understanding, we can provide a variety of options that can be selected according to the actual situation of their own a real development project in the latter, so far, we now the most important point is to see: _configureServicesDelegates of a bearing role.

Guess you like

Origin www.cnblogs.com/lex-wu/p/10782813.html