ASP.NET Core的源码解析中,Program.cs文件是如何实现应用程序启动流程的?

2026-03-30 13:341阅读0评论SEO资讯
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2375个文字,预计阅读时间需要10分钟。

ASP.NET Core的源码解析中,Program.cs文件是如何实现应用程序启动流程的?

创建一个ASP.NET Core项目,参考微软官方文档,非常详细:https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/start-mvc?view=aspnetcore-2.2tabs=visual-studio。完成后,将有一个Web项目,打开后是我的层面。

创建一个 asp.net core的项目

  1. 参考微软文档吧, 很详细: docs.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/start-mvc?view=aspnetcore-2.2&tabs=visual-studio

    ASP.NET Core的源码解析中,Program.cs文件是如何实现应用程序启动流程的?

  2. 创建完后会有个web项目, 打开后我这面的层级目录如下:

代码解析

//为什么关注这个类, 因为这里有main函数, 一般来说main函数都是程序启动的时候的启动类. 看一下这行代码: public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>();

看到这个 Main 函数时我顿时有种似曾相识的感觉, 这个不是 console application 中的 Main 函数么, 二者莫非有关联?

在我的理解, 其实asp.net core web application其本质应该就是一个 console application,然后通过内置的 web server 去处理 docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?tabs=aspnetcore2x&view=aspnetcore-2.2, 这里就不多写了.

那么回头来看 asp.net core 的 main 函数都做了什么?

  1. 创建 WebHostBuilder 对象
  2. 让这个 WebHostBuilder 对象 build一个 webhost 并run起来

创建 WebHostBuilder 对象

public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>();

分开来看, 先看第一部分 WebHost.CreateDefaultBuilder(args)

打开源码文件路径(..\AspNetCore-2.2.4\src\DefaultBuilder\src)(因为源码中很多叫WebHost的文件, 容易找不到), 我们来看一下 CreateDefaultBuilder 这个方法的源码:

/// <summary> /// Initializes a new instance of the <see cref="WebHostBuilder"/> class with pre-configured defaults. /// </summary> /// <remarks> /// The following defaults are applied to the returned <see cref="WebHostBuilder"/>: /// use Kestrel as the web server and configure it using the application's configuration providers, /// set the <see cref="IHostingEnvironment.ContentRootPath"/> to the result of <see cref="Directory.GetCurrentDirectory()"/>, /// load <see cref="IConfiguration"/> from 'appsettings.json' and 'appsettings.[<see cref="IHostingEnvironment.EnvironmentName"/>].json', /// load <see cref="IConfiguration"/> from User Secrets when <see cref="IHostingEnvironment.EnvironmentName"/> is 'Development' using the entry assembly, /// load <see cref="IConfiguration"/> from environment variables, /// load <see cref="IConfiguration"/> from supplied command line args, /// configure the <see cref="ILoggerFactory"/> to log to the console and debug output, /// and enable IIS integration. /// </remarks> /// <param name="args">The command line args.</param> /// <returns>The initialized <see cref="IWebHostBuilder"/>.</returns> public static IWebHostBuilder CreateDefaultBuilder(string[] args) { var builder = new WebHostBuilder(); if (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.ContentRootKey))) { builder.UseContentRoot(Directory.GetCurrentDirectory()); } if (args != null) { builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build()); } builder.UseKestrel((builderContext, options) => { options.Configure(builderContext.Configuration.GetSection("Kestrel")); }) .ConfigureAppConfiguration((hostingContext, config) => { var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); if (env.IsDevelopment()) { var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); if (appAssembly != null) { config.AddUserSecrets(appAssembly, optional: true); } } config.AddEnvironmentVariables(); if (args != null) { config.AddCommandLine(args); } }) .ConfigureLogging((hostingContext, logging) => { logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); logging.AddEventSourceLogger(); }) .ConfigureServices((hostingContext, services) => { // Fallback services.PostConfigure<HostFilteringOptions>(options => { if (options.AllowedHosts == null || options.AllowedHosts.Count == 0) { // "AllowedHosts": "localhost;127.0.0.1;[::1]" var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); // Fall back to "*" to disable. options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" }); } }); // Change notification services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>( new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration)); services.AddTransient<IStartupFilter, HostFilteringStartupFilter>(); }) .UseIIS() .UseIISIntegration() .UseDefaultServiceProvider((context, options) => { options.ValidateScopes = context.HostingEnvironment.IsDevelopment(); }); return builder; }

根据源码,我们来总结一下 CreateDefaultBuilder 所做的工作:

UseKestrel:使用Kestrel作为Web server。 UseContentRoot:指定Web host使用的content root(内容根目录),比如Views。默认为当前应用程序根目录。 ConfigureAppConfiguration:设置当前应用程序配置。主要是读取 appsettinggs.json 配置文件、开发环境中配置的UserSecrets、添加环境变量和命令行参数 。 ConfigureLogging:读取配置文件中的Logging节点,配置日志系统。 UseIISIntegration:使用IISIntegration 中间件。 UseDefaultServiceProvider:设置默认的依赖注入容器。

再看第二部分 .UseStartup<Startup>();

我们直接F12

// // Summary: // Specify the startup type to be used by the web host. // // Parameters: // hostBuilder: // The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure. // // Type parameters: // TStartup: // The type containing the startup methods for the application. // // Returns: // The Microsoft.AspNetCore.Hosting.IWebHostBuilder. public static IWebHostBuilder UseStartup<TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class;

当然这些不够, 我们的目的是知道底层是用怎么使用的startup

/// <summary> /// Specify the startup type to be used by the web host. /// </summary> /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param> /// <param name="startupType">The <see cref="Type"/> to be used.</param> /// <returns>The <see cref="IWebHostBuilder"/>.</returns> public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType) { var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name; return hostBuilder .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName) .ConfigureServices(services => { if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo())) { services.AddSingleton(typeof(IStartup), startupType); } else { services.AddSingleton(typeof(IStartup), sp => { var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>(); return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName)); }); } }); }

可以看到其实UseStartup方法返回的是IWebHostBuilder对象, 其中.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName) 是将 ApplicationKeystartupAssemblyName 缓存起来. 如下图:

然后.ConfigureServices(Action<IServiceCollection> configureServices) 实际上也是对list集合进行一个缓存操作, 但是注意这个方法的参数是一个委托Action, 实际上调用的时候传递的是一个lambda表达式, 我们需要看看这个表达式里的神奇操作:(我们主要关注 else 里面的部分, if中的没啥可说的.)

services.AddSingleton(typeof(IStartup), sp => { var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>(); return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName)); });

services是我们 asp.net core DI的核心ServiceCollection , 然后AddSingleton方法是它内部的一个静态方法

public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory);

所以我们看到的 sp 实际上是 IServiceProvider, 先暂时理解成是用来搞到 service 的.
然后精髓来了,

StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName)

参数我们差不多都知道代表什么. 但是这个LoadMethods 是干嘛的?? 我们看一下解释:

很长, 但是主要就是用于通过反射技术, 将我们自定义的StartUp.cs文件里面的方法都load到低层来. 然后顺便配置一下每次都有的那个service和创建一个request pipeline of the application

这里我觉得有必要将startup.cs文件中的两个function贴到下面方便我们后续的撸源码:

// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseCookiePolicy(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }

看完这两个方法, 我们都能明白一个事儿就是这两个方法都是运行时调用的. 至于为什么是运行时调用的, 咱们差不多都知道了, 因为底层build的时候反射获取这两个方法, 将两个方法的配置参数什么的配置进底层的service中.

这里有必要解释一下我们的自定义 startup 文件里面的参数含义, 因为这毕竟是暴露给我们开发者使用的, 所以应该多了解一下: IServiceCollection:当前容器中各服务的配置集合,ASP.NET Core内置的依赖注入容器。 IApplicationBuilder:用于构建应用程序的请求管道。 IHostingEnvironment:提供了当前的 EnvironmentName、WebRootPath 以及 ContentRoot等。

public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName) { var configureMethod = FindConfigureDelegate(startupType, environmentName); var servicesMethod = FindConfigureServicesDelegate(startupType, environmentName); var configureContainerMethod = FindConfigureContainerDelegate(startupType, environmentName); object instance = null; if (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic)) { instance = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType); } // The type of the TContainerBuilder. If there is no ConfigureContainer method we can just use object as it's not // going to be used for anything. var type = configureContainerMethod.MethodInfo != null ? configureContainerMethod.GetContainerType() : typeof(object); var builder = (ConfigureServicesDelegateBuilder) Activator.CreateInstance( typeof(ConfigureServicesDelegateBuilder<>).MakeGenericType(type), hostingServiceProvider, servicesMethod, configureContainerMethod, instance); return new StartupMethods(instance, configureMethod.Build(instance), builder.Build()); }

(反射已经用到了出神入化的地步) 想看怎么实现的就自己去看吧. 反正代码贴到这里, 意思明白了就行.

好了, 目前知道了.ConfigureService 的参数是干嘛的了... 那看看这个方法的底层要这个参数做啥了吧:

private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates; /// <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="IServiceCollection"/>.</param> /// <returns>The <see cref="IWebHostBuilder"/>.</returns> public IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices) { if (configureServices == null) { throw new ArgumentNullException(nameof(configureServices)); } return ConfigureServices((_, 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="IServiceCollection"/>.</param> /// <returns>The <see cref="IWebHostBuilder"/>.</returns> public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices) { if (configureServices == null) { throw new ArgumentNullException(nameof(configureServices)); } _configureServicesDelegates.Add(configureServices); return this; }

卧槽槽, 又是一个缓存....行吧, 底层各种搞缓存, 不就是DI的本质吗..这算基本搞懂了UseStartup..

创建 WebHostBuilder 对象完事了

那么WebHostBuilder创建出来后, 里面有两个缓存, 对startup的类型进行缓存之外, 还对startup的services进行缓存. 然后这个builder显然是要进行 build 的, 不然费那么大力气创建干嘛. 所以看下一篇吧, 解读后续操作.

本文共计2375个文字,预计阅读时间需要10分钟。

ASP.NET Core的源码解析中,Program.cs文件是如何实现应用程序启动流程的?

创建一个ASP.NET Core项目,参考微软官方文档,非常详细:https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/start-mvc?view=aspnetcore-2.2tabs=visual-studio。完成后,将有一个Web项目,打开后是我的层面。

创建一个 asp.net core的项目

  1. 参考微软文档吧, 很详细: docs.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/start-mvc?view=aspnetcore-2.2&tabs=visual-studio

    ASP.NET Core的源码解析中,Program.cs文件是如何实现应用程序启动流程的?

  2. 创建完后会有个web项目, 打开后我这面的层级目录如下:

代码解析

//为什么关注这个类, 因为这里有main函数, 一般来说main函数都是程序启动的时候的启动类. 看一下这行代码: public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>();

看到这个 Main 函数时我顿时有种似曾相识的感觉, 这个不是 console application 中的 Main 函数么, 二者莫非有关联?

在我的理解, 其实asp.net core web application其本质应该就是一个 console application,然后通过内置的 web server 去处理 docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel?tabs=aspnetcore2x&view=aspnetcore-2.2, 这里就不多写了.

那么回头来看 asp.net core 的 main 函数都做了什么?

  1. 创建 WebHostBuilder 对象
  2. 让这个 WebHostBuilder 对象 build一个 webhost 并run起来

创建 WebHostBuilder 对象

public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>();

分开来看, 先看第一部分 WebHost.CreateDefaultBuilder(args)

打开源码文件路径(..\AspNetCore-2.2.4\src\DefaultBuilder\src)(因为源码中很多叫WebHost的文件, 容易找不到), 我们来看一下 CreateDefaultBuilder 这个方法的源码:

/// <summary> /// Initializes a new instance of the <see cref="WebHostBuilder"/> class with pre-configured defaults. /// </summary> /// <remarks> /// The following defaults are applied to the returned <see cref="WebHostBuilder"/>: /// use Kestrel as the web server and configure it using the application's configuration providers, /// set the <see cref="IHostingEnvironment.ContentRootPath"/> to the result of <see cref="Directory.GetCurrentDirectory()"/>, /// load <see cref="IConfiguration"/> from 'appsettings.json' and 'appsettings.[<see cref="IHostingEnvironment.EnvironmentName"/>].json', /// load <see cref="IConfiguration"/> from User Secrets when <see cref="IHostingEnvironment.EnvironmentName"/> is 'Development' using the entry assembly, /// load <see cref="IConfiguration"/> from environment variables, /// load <see cref="IConfiguration"/> from supplied command line args, /// configure the <see cref="ILoggerFactory"/> to log to the console and debug output, /// and enable IIS integration. /// </remarks> /// <param name="args">The command line args.</param> /// <returns>The initialized <see cref="IWebHostBuilder"/>.</returns> public static IWebHostBuilder CreateDefaultBuilder(string[] args) { var builder = new WebHostBuilder(); if (string.IsNullOrEmpty(builder.GetSetting(WebHostDefaults.ContentRootKey))) { builder.UseContentRoot(Directory.GetCurrentDirectory()); } if (args != null) { builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build()); } builder.UseKestrel((builderContext, options) => { options.Configure(builderContext.Configuration.GetSection("Kestrel")); }) .ConfigureAppConfiguration((hostingContext, config) => { var env = hostingContext.HostingEnvironment; config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); if (env.IsDevelopment()) { var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); if (appAssembly != null) { config.AddUserSecrets(appAssembly, optional: true); } } config.AddEnvironmentVariables(); if (args != null) { config.AddCommandLine(args); } }) .ConfigureLogging((hostingContext, logging) => { logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); logging.AddConsole(); logging.AddDebug(); logging.AddEventSourceLogger(); }) .ConfigureServices((hostingContext, services) => { // Fallback services.PostConfigure<HostFilteringOptions>(options => { if (options.AllowedHosts == null || options.AllowedHosts.Count == 0) { // "AllowedHosts": "localhost;127.0.0.1;[::1]" var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); // Fall back to "*" to disable. options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" }); } }); // Change notification services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>( new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration)); services.AddTransient<IStartupFilter, HostFilteringStartupFilter>(); }) .UseIIS() .UseIISIntegration() .UseDefaultServiceProvider((context, options) => { options.ValidateScopes = context.HostingEnvironment.IsDevelopment(); }); return builder; }

根据源码,我们来总结一下 CreateDefaultBuilder 所做的工作:

UseKestrel:使用Kestrel作为Web server。 UseContentRoot:指定Web host使用的content root(内容根目录),比如Views。默认为当前应用程序根目录。 ConfigureAppConfiguration:设置当前应用程序配置。主要是读取 appsettinggs.json 配置文件、开发环境中配置的UserSecrets、添加环境变量和命令行参数 。 ConfigureLogging:读取配置文件中的Logging节点,配置日志系统。 UseIISIntegration:使用IISIntegration 中间件。 UseDefaultServiceProvider:设置默认的依赖注入容器。

再看第二部分 .UseStartup<Startup>();

我们直接F12

// // Summary: // Specify the startup type to be used by the web host. // // Parameters: // hostBuilder: // The Microsoft.AspNetCore.Hosting.IWebHostBuilder to configure. // // Type parameters: // TStartup: // The type containing the startup methods for the application. // // Returns: // The Microsoft.AspNetCore.Hosting.IWebHostBuilder. public static IWebHostBuilder UseStartup<TStartup>(this IWebHostBuilder hostBuilder) where TStartup : class;

当然这些不够, 我们的目的是知道底层是用怎么使用的startup

/// <summary> /// Specify the startup type to be used by the web host. /// </summary> /// <param name="hostBuilder">The <see cref="IWebHostBuilder"/> to configure.</param> /// <param name="startupType">The <see cref="Type"/> to be used.</param> /// <returns>The <see cref="IWebHostBuilder"/>.</returns> public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, Type startupType) { var startupAssemblyName = startupType.GetTypeInfo().Assembly.GetName().Name; return hostBuilder .UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName) .ConfigureServices(services => { if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType.GetTypeInfo())) { services.AddSingleton(typeof(IStartup), startupType); } else { services.AddSingleton(typeof(IStartup), sp => { var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>(); return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName)); }); } }); }

可以看到其实UseStartup方法返回的是IWebHostBuilder对象, 其中.UseSetting(WebHostDefaults.ApplicationKey, startupAssemblyName) 是将 ApplicationKeystartupAssemblyName 缓存起来. 如下图:

然后.ConfigureServices(Action<IServiceCollection> configureServices) 实际上也是对list集合进行一个缓存操作, 但是注意这个方法的参数是一个委托Action, 实际上调用的时候传递的是一个lambda表达式, 我们需要看看这个表达式里的神奇操作:(我们主要关注 else 里面的部分, if中的没啥可说的.)

services.AddSingleton(typeof(IStartup), sp => { var hostingEnvironment = sp.GetRequiredService<IHostingEnvironment>(); return new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName)); });

services是我们 asp.net core DI的核心ServiceCollection , 然后AddSingleton方法是它内部的一个静态方法

public static IServiceCollection AddSingleton(this IServiceCollection services, Type serviceType, Func<IServiceProvider, object> implementationFactory);

所以我们看到的 sp 实际上是 IServiceProvider, 先暂时理解成是用来搞到 service 的.
然后精髓来了,

StartupLoader.LoadMethods(sp, startupType, hostingEnvironment.EnvironmentName)

参数我们差不多都知道代表什么. 但是这个LoadMethods 是干嘛的?? 我们看一下解释:

很长, 但是主要就是用于通过反射技术, 将我们自定义的StartUp.cs文件里面的方法都load到低层来. 然后顺便配置一下每次都有的那个service和创建一个request pipeline of the application

这里我觉得有必要将startup.cs文件中的两个function贴到下面方便我们后续的撸源码:

// This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseCookiePolicy(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }

看完这两个方法, 我们都能明白一个事儿就是这两个方法都是运行时调用的. 至于为什么是运行时调用的, 咱们差不多都知道了, 因为底层build的时候反射获取这两个方法, 将两个方法的配置参数什么的配置进底层的service中.

这里有必要解释一下我们的自定义 startup 文件里面的参数含义, 因为这毕竟是暴露给我们开发者使用的, 所以应该多了解一下: IServiceCollection:当前容器中各服务的配置集合,ASP.NET Core内置的依赖注入容器。 IApplicationBuilder:用于构建应用程序的请求管道。 IHostingEnvironment:提供了当前的 EnvironmentName、WebRootPath 以及 ContentRoot等。

public static StartupMethods LoadMethods(IServiceProvider hostingServiceProvider, Type startupType, string environmentName) { var configureMethod = FindConfigureDelegate(startupType, environmentName); var servicesMethod = FindConfigureServicesDelegate(startupType, environmentName); var configureContainerMethod = FindConfigureContainerDelegate(startupType, environmentName); object instance = null; if (!configureMethod.MethodInfo.IsStatic || (servicesMethod != null && !servicesMethod.MethodInfo.IsStatic)) { instance = ActivatorUtilities.GetServiceOrCreateInstance(hostingServiceProvider, startupType); } // The type of the TContainerBuilder. If there is no ConfigureContainer method we can just use object as it's not // going to be used for anything. var type = configureContainerMethod.MethodInfo != null ? configureContainerMethod.GetContainerType() : typeof(object); var builder = (ConfigureServicesDelegateBuilder) Activator.CreateInstance( typeof(ConfigureServicesDelegateBuilder<>).MakeGenericType(type), hostingServiceProvider, servicesMethod, configureContainerMethod, instance); return new StartupMethods(instance, configureMethod.Build(instance), builder.Build()); }

(反射已经用到了出神入化的地步) 想看怎么实现的就自己去看吧. 反正代码贴到这里, 意思明白了就行.

好了, 目前知道了.ConfigureService 的参数是干嘛的了... 那看看这个方法的底层要这个参数做啥了吧:

private readonly List<Action<WebHostBuilderContext, IServiceCollection>> _configureServicesDelegates; /// <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="IServiceCollection"/>.</param> /// <returns>The <see cref="IWebHostBuilder"/>.</returns> public IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices) { if (configureServices == null) { throw new ArgumentNullException(nameof(configureServices)); } return ConfigureServices((_, 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="IServiceCollection"/>.</param> /// <returns>The <see cref="IWebHostBuilder"/>.</returns> public IWebHostBuilder ConfigureServices(Action<WebHostBuilderContext, IServiceCollection> configureServices) { if (configureServices == null) { throw new ArgumentNullException(nameof(configureServices)); } _configureServicesDelegates.Add(configureServices); return this; }

卧槽槽, 又是一个缓存....行吧, 底层各种搞缓存, 不就是DI的本质吗..这算基本搞懂了UseStartup..

创建 WebHostBuilder 对象完事了

那么WebHostBuilder创建出来后, 里面有两个缓存, 对startup的类型进行缓存之外, 还对startup的services进行缓存. 然后这个builder显然是要进行 build 的, 不然费那么大力气创建干嘛. 所以看下一篇吧, 解读后续操作.