Written in front

The .NET Core application is directly visible in the Startup and Program classes, which are the starting point for .NET Core applications. By using Startup, you can configure the pipeline for all requests made to the application, while also reducing the dependency of .NET applications on a single server, allowing us to focus more on multi-server-centric development mode.

Startup discussion

Starup’s role

Startup is a concept that is required in ASP.NET Core programs. The Startup class itself can use a variety of modifiers (public, protect, private, internal) as an entry point to the ASP.NET Core application, which is related to the application. The configured function or interface.

Although the class name we use in the program is Startup, it should be noted that Startup is an abstract concept , you can name it other, such as MyAppStartup or other names, as long as you start your definition in the Program class. The startup class is fine.

Of course, if you don’t want to write Startup, you can configure the service and request processing pipeline in the Program class. Please see the 5th floor of the comment area.

The following is based on the syntax provided in the ASP.NET Core Preview 3 template:

   1:  public class Program
   2:   {
   3:      public static void Main(string[] args)
   4:       {
   5:          CreateHostBuilder(args).Build().Run();
   6:       }
   7:   
   8:      public static IHostBuilder CreateHostBuilder(string[] args) =>
   9:          Host.CreateDefaultBuilder(args)
  10:              .ConfigureWebHostDefaults(webBuilder =>
  11:               {
  12:                  webBuilder.UseStartup<Startup>();
  13:               });
  14:   }

No matter what you name, just configure the generic class in webBuilder.UseStartup<>() to be the entry class you defined;

Startup writing specification

Here’s how to write Startup in the ASP.NET Core 3.0 Preview 3 template:

   1:  // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
   2:  public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
   3:   {
   4:      if (env.IsDevelopment())
   5:       {
   6:          app.UseDeveloperExceptionPage();
   7:       }
   8:      else
   9:       {
  10:          // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
  11:          app.UseHsts();
  12:       }
  13:   
  14:      app.UseHttpsRedirection();
  15:   
  16:      app.UseRouting(routes =>
  17:       {
  18:          routes.MapControllers();
  19:       });
  20:   
  21:      app.UseAuthorization();
  22:   }

As you can see from the above code, the Startup class generally includes

  • Constructor: Through our previous development experience, we can know that this constructor can include multiple objects
    • IConfiguration: Represents a set of key/value application configuration properties.
    • IApplicationBuilder: is an interface that contains properties and methods related to the current environment. It is used to get environment variables in the application.
    • IHostingEnvironment: is an interface that contains information about the web hosting environment in which the application is running. Using this interface method, we can change the behavior of the application.
    • ILoggerFactory: is the interface that provides configuration for the logging system in ASP.NET Core. It also creates an instance of the logging system.
  • ConfigureServices
  • Configure

When Startup creates a service, it performs a dependency registration service to use these dependencies elsewhere in the application. ConfigureServices is used to register services, and the Configure method allows us to add middleware and services to the HTTP pipeline. This is why ConfigureServices is called before Configure.

ConfigureServices

When the alternative method, non-mandatory constraints, it is mainly used to support or dependency injection ApplicationServices throughout the application, the method must be public, which is a typical pattern for all calling Add{Service}methods, including solid frame main scene, authentication and MVC registration service:

   1:  services.AddDbContext<ApplicationDbContext>(options =>options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
   2:  services.AddDefaultIdentity<IdentityUser>().AddDefaultUI(UIFramework.Bootstrap4).AddEntityFrameworkStores<ApplicationDbContext>();
   3:  services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
   4:   // Add application services. Here is mainly to register IOC services.
   5:  services.AddTransient<IEmailSender, AuthMessageSender>();
   6:  services.AddTransient<ISmsSender, AuthMessageSender>();

Configure

This method is mainly used to define how the application responds to each HTTP request, that is, we can control the ASP.NET pipeline and can also be used to configure middleware in the HTTP pipeline. Each middleware component in the request pipeline is responsible for invoking the next component in the pipeline or, if appropriate, shorting the chain. If there is no short circuit in the middleware chain, each middleware has a second chance to process the request before sending it to the client.

This method accepts IApplicationBuilder as a parameter and can also receive other optional parameters such as IHostingEnvironment and ILoggerFactory.

In general, you can use the service as long as it is registered to the configureServices method.

   1:  app.UseDeveloperExceptionPage();   
   2:  app.UseHsts();   
   3:  app.UseHttpsRedirection();   
   4:  app.UseRouting(routes =>   
   5:   {   
   6:      routes.MapControllers();   
   7:   });   
   8:  app.UseAuthorization();

Extended Startup method

Use the IStartupFilter to extend the Startup function and use the IStartupFilter to configure the middleware at the beginning or end of the application’s Configure middleware pipeline. IStartupFilter helps ensure that middleware runs middleware before and after adding middleware at the beginning or end of the application request processing pipeline.

The following is the source code of IStartupFilter. We can know from the source code that the interface has an Action<IApplicationBuilder> type and is named Configure. Since the incoming parameter type and the return type are the same, this guarantees the transferability and order of the extension. The specific demo code can be parameterized MSDN.

   1:  using System;   
   2:  using Microsoft.AspNetCore.Builder;   
   3:    
   4:  namespace Microsoft.AspNetCore.Hosting   
   5:   {   
   6:     IStartupFilter    public  interface
   7:     {   
   8:        Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next);   
   9:     }  
  10:   }

How is Startup registered and executed?

This paragraph of text, I just want to know more about its internal mechanism, if I don’t understand it, it does not affect our normal writing of .NET Core applications.

UseStartup source

ASP.NET Core calls the Startup type by calling the IWebHostBuilder.UseStartup method. Note that the Startup is an abstract concept at the beginning. Let’s take a look at the source code:

   1:  /// <summary>
   2:   /// Specify the startup type to be used by the web host.
   3:   /// </summary>
   4:   /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
   5:   /// <param name="startupType">The <see cref="Type"/> to be used.</param>
   6:   /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
   7:   public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType)
   8:   {
   9:       var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name;
  10:   
  11:      hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName);
  12:   
  13:      // Light up the GenericWebHostBuilder implementation
  14:      if (hostBuilder is ISupportsStartup supportsStartup)
  15:       {
  16:          return supportsStartup.UseStartup(startupType);
  17:       }
  18:   
  19:      return hostBuilder
  20:          .ConfigureServices(services =>
  21:           {
  22:              if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
  23:               {
  24:                  services.AddSingleton(typeof(IStartup), startupType);
  25:               }
  26:              else
  27:               {
  28:                  services.AddSingleton(typeof(IStartup), sp =>
  29:                   {
  30:                      var hostingEnvironment = sp.GetRequiredService<IHostEnvironment>();
  31:                      return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName));
  32:                   });
  33:               }
  34:           });
  35:   }
  36:   
  37:  /// <summary>
  38:  /// Specify the startup type to be used by the web host.
  39:  /// </summary>
  40:  /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param>
  41:  /// <typeparam name ="TStartup">The type containing the startup methods for the application.</typeparam>
  42:  /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
  43:  public static IWebHostBuilder UseStartup<TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class
  44:   {
  45:      return hostBuilder.UseStartup(typeof(TStartup));
  46:   }

Create a Startup instance

   1:  /// <summary>
   2:  /// Adds a delegate for configuring additional services for the host or web application. This may be called
   3:  /// multiple times.
   4:  /// </summary>
   5:  /// <param name="configureServices">A delegate for configuring the <see cref="IServiceCollection"/>.</param>
   6:  /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
   7:  public IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices)
   8:   {
   9:      if (configureServices == null)
  10:       {
  11:          throw new ArgumentNullException(nameof(configureServices));
  12:       }
  13:   
  14:      return ConfigureServices((_, services) => configureServices(services));
  15:   }
  16:   
  17:  /// <summary>
  18:  /// Adds a delegate for configuring additional services for the host or web application. This may be called
  19:  /// multiple times.
  20:  /// </summary>
  21:  /// <param name="configureServices">A delegate for configuring the <see cref="IServiceCollection"/>.</param>
  22:  /// <returns>The <see cref="IWebHostBuilder"/>.</returns>
  23:  public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices)
  24:   {
  25:      _configureServices += configureServices;
  26:      return this;
  27:   }

The definition and registration method of ConfigureServices is implemented in IWebHostBuilder.ConfigureServices. At the same time, you can pay attention to the 25 lines of code, and explain to you the root cause of the merged Startup’s ConfigureServices method. There are also a lot of abstract delegates used here.

There are Build methods in this class, I will not post the code, just need to know, the main process starts here. The next important method is BuildCommonServices, which adds some public framework-level services to the current ServiceCollection. The following is part of the code. For details, see
WebHostBuilder

.

   1:  try
   2:   {
   3:      var startupType = StartupLoader.FindStartupType(_options.StartupAssembly, _hostingEnvironment.EnvironmentName);
   4:   
   5:      if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo()))
   6:       {
   7:          services.AddSingleton(typeof(IStartup), startupType);
   8:       }
   9:      else
  10:       {
  11:          services.AddSingleton(typeof(IStartup), sp =>
  12:           {
  13:              var hostingEnvironment = sp.GetRequiredService<IHostEnvironment>();
  14:              var methods = StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName);
  15:              return new ConventionBasedStartup(methods);
  16:           });
  17:       }
  18:   }
  19:  catch (Exception ex)
  20:   {
  21:      var capture = ExceptionDispatchInfo.Capture(ex);
  22:      services.AddSingleton<IStartup>(_ =>
  23:       {
  24:          capture.Throw();
  25:          return null;
  26:       });
  27:   }
Thus, if our Startup class implements IStartup directly, it can and will be registered directly as an implementation type of IStartup. However, the ASP.NET Core template code does not implement IStartup. It is more of a convention and calls the delegate via DI. There are two other methods for calling the constructor in Startup.
At the same time, the above code also shows how to create a Startup type, which uses the static method StartupLoader.LoadMethods class to generate a StartupMethods instance.

ConfigureServices和Configure

When the WebHost is initialized, the framework will look for the corresponding method. Here, we mainly look at the source code. The core method is StartupLoader.FindMethods.
   1:  private static MethodInfo FindMethod(Type startupType, string methodName, string environmentName, Type returnType = null, bool required = true)
   2:   {
   3:      var methodNameWithEnv = string.Format(CultureInfo.InvariantCulture, methodName, environmentName);
   4:      var methodNameWithNoEnv = string.Format(CultureInfo.InvariantCulture, methodName, "");
   5:   
   6:      var methods = startupType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
   7:      var selectedMethods = methods.Where(method => method.Name.Equals(methodNameWithEnv, StringComparison.OrdinalIgnoreCase)).ToList();
   8:      if (selectedMethods.Count > 1)
   9:       {
  10:          throw new InvalidOperationException(string.Format("Having multiple overloads of method '{0}' is not supported.", methodNameWithEnv));
  11:       }
  12:      if (selectedMethods.Count == 0)
  13:       {
  14:          selectedMethods = methods.Where(method => method.Name.Equals(methodNameWithNoEnv, StringComparison.OrdinalIgnoreCase)).ToList();
  15:          if (selectedMethods.Count > 1)
  16:           {
  17:              throw new InvalidOperationException(string.Format("Having multiple overloads of method '{0}' is not supported.", methodNameWithNoEnv));
  18:           }
  19:       }
  20:   
  21:      var methodInfo = selectedMethods.FirstOrDefault();
  22:      if (methodInfo == null)
  23:       {
  24:          if (required)
  25:           {
  26:              throw new InvalidOperationException(string.Format("A public method named '{0}' or '{1}' could not be found in the '{2}' type.",
  27:                  methodNameWithEnv,
  28:                  methodNameWithNoEnv,
  29:                  startupType.FullName));
  30:   
  31:           }
  32:          return null;
  33:       }
  34:      if (returnType != null && methodInfo.ReturnType != returnType)
  35:       {
  36:          if (required)
  37:           {
  38:              throw new InvalidOperationException(string.Format("The '{0}' method in the type '{1}' must have a return type of '{2}'.",
  39:                   methodInfo.Name,
  40:                  startupType.FullName,
  41:                   returnType.Name));
  42:           }
  43:          return null;
  44:       }
  45:      return methodInfo;
  46:   }
The first delegate it looks for is ConfigureDelegate, which will be used to build the middleware pipeline for the application. FindMethod has done most of the work, see the 
StartupLoader for

 specific code . This method finds the response in the Startup class based on the methodName parameter passed to it.
We know that the definition of Startup is more of a convention, so I will look for Configure and ConfigureServices. Of course, through the source code I also know that in addition to providing a standard "Configure" method, we can also find the Response's Configure and ConfigureServices through the environment configuration. Basically, what we finally found is the ConfigureContainerDelegate.
Next, a more important method is LoadMethods
   1:  public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName)
   2:   {
   3:      var configureMethod = FindConfigureDelegate(startupType, environmentName);
   4:   
   5:      var servicesMethod = FindConfigureServicesDelegate(startupType, environmentName);
   6:      var configureContainerMethod = FindConfigureContainerDelegate(startupType, environmentName);
   7:   
   8:      object instance = null;
   9:      if (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic))
  10:       {
  11:          instance = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType);
  12:       }
  13:   
  14:      // The type of the TContainerBuilder. If there is no ConfigureContainer method we can just use object as it's not
  15:      // going to be used for anything.
  16:      var type = configureContainerMethod.MethodInfo != null ? configureContainerMethod.GetContainerType() : typeof(object);
  17:   
  18:      var builder = (ConfigureServicesDelegateBuilder) Activator.CreateInstance(
  19:          typeof(ConfigureServicesDelegateBuilder<>).MakeGenericType(type),
  20:          hostingServiceProvider,
  21:          servicesMethod,
  22:          configureContainerMethod,
  23:          instance);
  twenty four:   
  25:      return new StartupMethods(instance, configureMethod.Build(instance), builder.Build());
  26:   }
 
This method finds the corresponding method. Since the Startup is not registered in the DI, it will call GetServiceOrCreateInstance to create a Startup instance. At this time, the constructor is also parsed here.
Through a series of calls, it finally reaches the ConfigureServicesBuilder.Invoke. The Invoke method uses reflection to get and check the parameters required by the ConfigureServices method defined on the Startup class.
   1:  private IServiceProvider InvokeCore(object instance, IServiceCollection services)
   2:   {
   3:      if (MethodInfo == null)
   4:       {
   5:          return null;
   6:       }
   7:   
   8:      // Only support IServiceCollection parameters
   9:      var parameters = MethodInfo.GetParameters();
  10:      if (parameters.Length > 1 ||
  11:          parameters.Any(p => p.ParameterType != typeof(IServiceCollection)))
  12:       {
  13:          throw new InvalidOperationException("The ConfigureServices method must either be parameterless or take only one parameter of type IServiceCollection.");
  14:       }
  15:   
  16:      var arguments = new object[MethodInfo.GetParameters().Length];
  17:   
  18:      if (parameters.Length > 0)
  19:       {
  20:          arguments[0] = services;
  21:       }
  twenty two:   
  23:      return MethodInfo.Invoke(instance, arguments) as IServiceProvider;
  24:   }

Finally, let’s take a look at the ConfigureBuilder class, which takes an Action<IApplicationBuilder> delegate variable containing a set of wrapped Configure methods for each IStartupFilter, and the last one is the delegate of the Startup.Configure method. At this point, the configuration chain that is called first hits the AutoRequestServicesStartupFilter.Configure method. The delegate chain is used as the next operation, and then the ConventionBasedStartup.Configure method is called. This will call ConfigureDelegate on its local StartupMethods object.

   1:  private void Invoke(object instance, IApplicationBuilder builder)
   2:   {
   3:      // Create a scope for Configure, this allows creating scoped dependencies
   4:      // without the hassle of manually creating a scope.
   5:      using (var scope = builder.ApplicationServices.CreateScope())
   6:       {
   7:           var serviceProvider = scope.ServiceProvider;
   8:          var parameterInfos = MethodInfo.GetParameters();
   9:          var parameters = new object[parameterInfos.Length];
  10:          for (var index = 0; index < parameterInfos.Length; index++)
  11:           {
  12:               var parameterInfo = parameterInfos [index];
  13:              if (parameterInfo.ParameterType == typeof(IApplicationBuilder))
  14:               {
  15:                  parameters[index] = builder;
  16:               }
  17:              else
  18:               {
  19:                  try
  20:                   {
  21:                      parameters[index] = serviceProvider.GetRequiredService(parameterInfo.ParameterType);
  22:                   }
  23:                  catch (Exception ex)
  24:                   {
  25:                      throw new Exception(string.Format(
  26:                          "Could not resolve a service of type '{0}' for the parameter '{1}' of method '{2}' on type '{3}'.",
  27:                          parameterInfo.ParameterType.FullName,
  28:                           parameterInfo.Name,
  29:                           MethodInfo.Name,
  30:                          MethodInfo.DeclaringType.FullName), ex);
  31:                   }
  32:               }
  33:           }
  34:          MethodInfo.Invoke(instance, parameters);
  35:       }
  36:   }

The Startup Configure method calls the corresponding parameter parsed by the ServiceProvider. This method can also be used to add middleware to the application pipeline using IApplicationBuilder. The final RequestDelegate is built and returned from IApplicationBuilder, and the WebHost initialization is complete.

Orignal link:https://www.cnblogs.com/edison0621/p/10743228.html