ASP.NET Core 应用程序启动

启动流程

  1. 创建并默认配置HostBuilder对象,用来构建Host的对象。
  2. HostBuilder对象配置参数,包括注册的服务,组件,请求处理管道相关内容(Startup 类 配置服务和应用的请求管道),增加了ASP.NET Core的运行时设置。
  3. 配置好的HostBuilder对象构建Host对象。
  4. Host对象启动.Run()。
  5. 控制台成为ASP.NET Core的web应用程序,开始监听请求。
  6. Host监听到请求后,请求需要经过Host配置的请求处理管道进行相关处理。
    在这里插入图片描述

什么是Host

Host作为一个可以用来托管web程序的服务对象,用于封装应用资源以及应用程序启动和生存期管理,其主要功能包括配置初始化、创建托管环境和Host通用上下文、依赖注入等。封装应用资源有:

  • 依赖注入框架 DI
  • Logging日志
  • Configuration 配置
  • 托管服务:IHostedService服务接口的实现

WebHost即托管ASP.NET Core Web程序的服务对象。在低于 3.0 的 ASP.NET Core 版本中,WebHost用于 HTTP 工作负载。.NET Core提供WebHost的同时,还提供了一个通用Host的概念。通用Host和WebHost提供了类似的架构和功能,包含依赖注入框架DI、日志、配置、各类应用(托管服务)。通用Host的出现,给了我们更多开发的选择,比如说后台处理任务场景。在.NET Core3.1版本后,微软不再建议将 WebHost用于 Web 应用,直接使用通用Host来替换WebHost。
在这里插入图片描述

在这里插入图片描述在这里插入图片描述

Host构建

从应用程序启动函数Main函数中看出构建Host的流程是首先调用接收命令行参数args的CreateHostBuilder方法,返回一个HostBuilder对象,然后HostBuilder对象.Build()方法完成构建Host对象。

Host.CreateDefaultBuilder(args)

Host.CreateDefaultBuilder方法内部操作对HostBuilder默认配置

  • 将内容根目录(contentRootPath)设置为由 GetCurrentDirectory 返回的路径。
  • 通过以下源加载主机配置
    • 环境变量(DOTNET_前缀)配置
    • 命令行参数配置
  • 通过以下对象加载应用配置
    • appsettings.json
    • appsettings.{Environment}.json
    • 密钥管理器 当应用在 Development 环境中运行时
    • 环境变量
    • 命令行参数
  • 添加日志记录提供程序
    • 控制台
    • 调试
    • EventSource
    • EventLog( Windows环境下)
  • 当环境为“开发”时,启用范围验证和依赖关系验证。
public static IHostBuilder CreateDefaultBuilder(string[] args)
        {
            var builder = new HostBuilder();
  
            builder.UseContentRoot(Directory.GetCurrentDirectory());//将内容根目录(contentRootPath)设置为由 GetCurrentDirectory 返回的路径。
            builder.ConfigureHostConfiguration(config =>
            {
                config.AddEnvironmentVariables(prefix: "DOTNET_");//环境变量(DOTNET_前缀)配置
                if (args != null)
                {
                    config.AddCommandLine(args);//命令行参数配置
                }
            });
   			// 通过以下对象加载应用配置 
            builder.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() && !string.IsNullOrEmpty(env.ApplicationName))
                {
                    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) =>
            {
                var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
  
                // IMPORTANT: This needs to be added *before* configuration is loaded, this lets
                // the defaults be overridden by the configuration.
                if (isWindows)
                {
                    // Default the EventLogLoggerProvider to warning or above
                    logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
                }
  
                logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
                logging.AddConsole();
                logging.AddDebug();
                logging.AddEventSourceLogger();
  
                if (isWindows)
                {
                    // Add the EventLogLoggerProvider on windows machines
                    logging.AddEventLog();
                }
            })
            //当环境为“开发”时,启用范围验证和依赖关系验证。
            .UseDefaultServiceProvider((context, options) =>
            {
                var isDevelopment = context.HostingEnvironment.IsDevelopment();
                options.ValidateScopes = isDevelopment;
                options.ValidateOnBuild = isDevelopment;
            });
  
            return builder;
        }

IHostBuilder.ConfigureWebHostDefaults

完成构造HostBuilder,然后代码继续调用了HostBuilder.ConfigureWebHostDefaults方法。通过GenericWebHostBuilder对HostBuilder增加ASP.NET Core的运行时设置。

IHostBuilder.ConfigureWebHostDefaults

public static class GenericHostBuilderExtensions
{
    public static IHostBuilder ConfigureWebHostDefaults(this IHostBuilder builder, Action<IWebHostBuilder> configure)
    {
        if (configure is null)
        {
            throw new ArgumentNullException(nameof(configure));
        }

        return builder.ConfigureWebHost(webHostBuilder =>
        {
            WebHost.ConfigureWebDefaults(webHostBuilder);

            configure(webHostBuilder);
        });
    }
}

IHostBuilder.ConfigureWebHost

public static class GenericHostWebHostBuilderExtensions
{
    public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<IWebHostBuilder> configure)
    {
        var webhostBuilder = new GenericWebHostBuilder(builder);
        configure(webhostBuilder);
        builder.ConfigureServices((context, services) => services.AddHostedService<GenericWebHostService>());
        return builder;
    }
}

GenericHostWebHostBuilderExtensions类中IHostBuilder的扩展方法ConfigureWebHost实现了对IWebHostBuilder的依赖注入,即将GenericWebHostBuilder实例传入方法ConfigureWebHostDefaults内部。通过GenericWebHostBuilder的构造函数GenericWebHostBuilder(buillder),将已有的HostBuilder增加了ASP.NET Core运行时设置。

总结IHostBuilder.ConfigureWebHostDefaults做了两件事情:

  1. 扩展IHostBuilder增加ConfigureWebHost,引入IWebHostBuilder的实现GenericWebHostBuilder,将已有的HostBuilder增加ASP.NET Core运行时的设置。
  2. ConfigureWebHost代码中的configure(webhostBuilder):对注入的IWebHostBuilder,调用 WebHost.ConfigureWebDefaults(webHostBuilder),启用各类设置。

WebHost.ConfigureWebDefaults(webHostBuilder)内部实现了:

  • 前缀为 ASPNETCORE_ 的环境变量加载主机配置
  • 将 Kestrel作为默认的Web服务器
  • 添加HostFiltering中间件(主机筛选中间件)
  • 如果ASPNETCORE_FORWARDEDHEADERS_ENABLED=true,添加转接头中间件ForwardedHeaders
  • 启用IIS集成
internal static void ConfigureWebDefaults(IWebHostBuilder builder)
       {
           builder.ConfigureAppConfiguration((ctx, cb) =>
           {
               if (ctx.HostingEnvironment.IsDevelopment())
               {
                   StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment, ctx.Configuration);
               }
           });
           builder.UseKestrel((builderContext, options) =>
           {
               options.Configure(builderContext.Configuration.GetSection("Kestrel"));
           })
           .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>();
  
               if (string.Equals("true", hostingContext.Configuration["ForwardedHeaders_Enabled"], StringComparison.OrdinalIgnoreCase))
               {
                   services.Configure<ForwardedHeadersOptions>(options =>
                   {
                       options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
                       // Only loopback proxies are allowed by default. Clear that restriction because forwarders are
                       // being enabled by explicit configuration.
                       options.KnownNetworks.Clear();
                       options.KnownProxies.Clear();
                   });
  
                   services.AddTransient<IStartupFilter, ForwardedHeadersStartupFilter>();
               }
  
               services.AddRouting();
           })
           .UseIIS()
           .UseIISIntegration();
       }

webBuilder.UseStartup();

返回IHostBuilder.ConfigureWebHostDefaults代码中的configure(webHostBuilder):执行Program类中的webBuilder.UseStartup();

CreateHostBuilder(args).Build()

Build过程:

  • BuildHostConfiguration: 构造配置系统,初始化 IConfiguration _hostConfiguration;
  • CreateHostingEnvironment:构建HostingEnvironment环境信息,包含ApplicationName、EnvironmentName、ContentRootPath等
  • CreateHostBuilderContext:创建主机Build上下文HostBuilderContext,上下文中包含:HostingEnvironment和Configuration
  • BuildAppConfiguration:构建应用程序配置
  • CreateServiceProvider:创建依赖注入服务提供程序, 即依赖注入容器
        public IHost Build()
        {
            if (_hostBuilt)
            {
                throw new InvalidOperationException("Build can only be called once.");
            }
            _hostBuilt = true;

            BuildHostConfiguration();
            CreateHostingEnvironment();
            CreateHostBuilderContext();
            BuildAppConfiguration();
            CreateServiceProvider();

            return _appServices.GetRequiredService<IHost>();
        }

Host运行

IHost.Run

// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.Extensions.Hosting
{
    public static class HostingAbstractionsHostExtensions
    {
        /// <summary>
        /// Starts the host synchronously.
        /// </summary>
        /// <param name="host">The <see cref="IHost"/> to start.</param>
        public static void Start(this IHost host)
        {
            host.StartAsync().GetAwaiter().GetResult();
        }

        /// <summary>
        /// Attempts to gracefully stop the host with the given timeout.
        /// </summary>
        /// <param name="host">The <see cref="IHost"/> to stop.</param>
        /// <param name="timeout">The timeout for stopping gracefully. Once expired the
        /// server may terminate any remaining active connections.</param>
        /// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
        public static Task StopAsync(this IHost host, TimeSpan timeout)
        {
            return host.StopAsync(new CancellationTokenSource(timeout).Token);
        }

        /// <summary>
        /// Block the calling thread until shutdown is triggered via Ctrl+C or SIGTERM.
        /// </summary>
        /// <param name="host">The running <see cref="IHost"/>.</param>
        public static void WaitForShutdown(this IHost host)
        {
            host.WaitForShutdownAsync().GetAwaiter().GetResult();
        }

        /// <summary>
        /// Runs an application and block the calling thread until host shutdown.
        /// </summary>
        /// <param name="host">The <see cref="IHost"/> to run.</param>
        public static void Run(this IHost host)
        {
            host.RunAsync().GetAwaiter().GetResult();
        }

        /// <summary>
        /// Runs an application and returns a Task that only completes when the token is triggered or shutdown is triggered.
        /// </summary>
        /// <param name="host">The <see cref="IHost"/> to run.</param>
        /// <param name="token">The token to trigger shutdown.</param>
        /// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
        public static async Task RunAsync(this IHost host, CancellationToken token = default)
        {
            try
            {
                await host.StartAsync(token);

                await host.WaitForShutdownAsync(token);
            }
            finally
            {
                if (host is IAsyncDisposable asyncDisposable)
                {
                    await asyncDisposable.DisposeAsync();
                }
                else
                {
                    host.Dispose();
                }

            }
        }

        /// <summary>
        /// Returns a Task that completes when shutdown is triggered via the given token.
        /// </summary>
        /// <param name="host">The running <see cref="IHost"/>.</param>
        /// <param name="token">The token to trigger shutdown.</param>
        /// <returns>The <see cref="Task"/> that represents the asynchronous operation.</returns>
        public static async Task WaitForShutdownAsync(this IHost host, CancellationToken token = default)
        {
            var applicationLifetime = host.Services.GetService<IHostApplicationLifetime>();

            token.Register(state =>
            {
                ((IHostApplicationLifetime)state).StopApplication();
            },
            applicationLifetime);

            var waitForStop = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
            applicationLifetime.ApplicationStopping.Register(obj =>
            {
                var tcs = (TaskCompletionSource<object>)obj;
                tcs.TrySetResult(null);
            }, waitForStop);

            await waitForStop.Task;

            // Host will use its default ShutdownTimeout if none is specified.
            await host.StopAsync();
        }
    }
}

IHost.Run内部调用了Host.StartAsync方法,在StartAsync方法内部启动了DI依赖注入容器中所有注册的服务。

参考:
https://docs.microsoft.com/zh-cn/aspnet/core/fundamentals/host/web-host?view=aspnetcore-5.0
https://www.cnblogs.com/edison0621/p/11025310.html
https://www.cnblogs.com/tianqing/p/12726181.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值