冠军

导航

ASP.NET Core IHostBuilder

HostBuilder

很显然,HostBuildr 就是用来构建 Host 的构建器。

IHostBuilder 定义

通过 Build() 方法,构建器返回构建的 IHost 对象实例。

具体怎么构建呢?IHostBuilder 提供了多个扩展点,允许我们对构建过程进行扩展。

  • ConfigureHostConfiguration
  • ConfigureAppConfiguration
  • ConfigureServices
  • UseServiceProviderFactory
  • ConfigureContainer

对于应用配置:

  • ConfigureHostConfiguration
  • ConfigureAppConfiguration

对于容器管理:

  • 使用 ConfigureServices 方法添加服务注册,
  • 利用 UseServiceProviderFactory 方法注册 IServiceProviderFactory 工厂
  • 以及利用 ConfigureContainer 方法对依赖注入容器进行进一步的配置

IHostBuilder 接口定义,在 GitHub 查看 IHostBuilder 源码

namespace Microsoft.Extensions.Hosting
{
    public interface IHostBuilder
    {
        IDictionary<object, object> Properties { get; }
        IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate);
        IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate);
        IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate);
        IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory);
        IHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory);
        IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate);

        IHost Build();
    }
}

默认的 HostBuilder 实现

IHostBuilder 的默认实现 是 HostBuilder,在 GitHub 中查看 HostBuilder 源码

针对扩展点,提供了默认的实现来供我们调用,以 ConfigureAppConfiguration 为例,

private List<Action<HostBuilderContext, IConfigurationBuilder>> _configureAppConfigActions 
      = new List<Action<HostBuilderContext, IConfigurationBuilder>>();

public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate)
 {
     _configureAppConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
     return this;
 }

可以看到内部提供了一个 Action 的集合,每次调用 ConfigureAppConfiguration() 方法,是将我们提供的 Action 追加到这个集合中。这意味着我们可以多次调用这个方法。

在源码中可以看到内部提供了多个集合。

private List<Action<IConfigurationBuilder>> _configureHostConfigActions = new List<Action<IConfigurationBuilder>>();
private List<Action<HostBuilderContext, IConfigurationBuilder>> _configureAppConfigActions = new List<Action<HostBuilderContext, IConfigurationBuilder>>();
private List<Action<HostBuilderContext, IServiceCollection>> _configureServicesActions = new List<Action<HostBuilderContext, IServiceCollection>>();

其中最重要的就是 Build 的实现了。

/// <summary>
/// Run the given actions to initialize the host. This can only be called once.
/// </summary>
/// <returns>An initialized <see cref="IHost"/></returns>
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>();
}

Build方法的内容也很简单,只有一个基本检查和六个步骤。

  1. 首先检查是否重复创建了IHost对象,每个IHostBuilder对象只能运行一次Build方法,否则抛出异常,确保我们不会重复创建IHost对象。
  2. 第二步初始化创建IHost对象过程中需要使用到的基本配置信息。
  3. 第三步初始化应用程序的Microsoft.Extensions.Hosting.IHostEnvironment对象,用来向应用程序提供程序运行环境信息(Development、Stage、Production等)。
  4. 第四步初始化(根据第二部初始化的基本配置信息)创建IHost对象所需要的所有上下文对象。
  5. 创建IOC容器,并将默认的IHost对象(Microsoft.Extensions.Hosting.Internal.Host)注入到IOC容易中。
  6. 然后将IHost对象从IOC容器里取出并返回给方法的调用方。

所以,对于 HostBuilder 来说,我们需要的就是通过这些扩展点来注册各用来配置这个 Builder 的 Action,它们会在 HostBuilder 的 Build() 方法被调用的时候,依此被调用执行,以完成最终 Host 的构建,最终构建出 Host 对象本身。

这里面的 CreateServiceProvider() 也值得一看。这里面构建了 ServiceCollection 对象实例,从此以后就可以使用依赖注入了。

private void CreateServiceProvider()
{
    var services = new ServiceCollection();
#pragma warning disable CS0618 // Type or member is obsolete
    services.AddSingleton<IHostingEnvironment>(_hostingEnvironment);
#pragma warning restore CS0618 // Type or member is obsolete
    services.AddSingleton<IHostEnvironment>(_hostingEnvironment);
    services.AddSingleton(_hostBuilderContext);
    // register configuration as factory to make it dispose with the service provider
    services.AddSingleton(_ => _appConfiguration);
#pragma warning disable CS0618 // Type or member is obsolete
    services.AddSingleton<IApplicationLifetime>(s => (IApplicationLifetime)s.GetService<IHostApplicationLifetime>());
#pragma warning restore CS0618 // Type or member is obsolete
    services.AddSingleton<IHostApplicationLifetime, ApplicationLifetime>();
    services.AddSingleton<IHostLifetime, ConsoleLifetime>();
    services.AddSingleton<IHost, Internal.Host>();
    services.AddOptions();
    services.AddLogging();

    foreach (var configureServicesAction in _configureServicesActions)
    {
        configureServicesAction(_hostBuilderContext, services);
    }

    var containerBuilder = _serviceProviderFactory.CreateBuilder(services);

    foreach (var containerAction in _configureContainerActions)
    {
        containerAction.ConfigureContainer(_hostBuilderContext, containerBuilder);
    }

    _appServices = _serviceProviderFactory.CreateServiceProvider(containerBuilder);

    if (_appServices == null)
    {
        throw new InvalidOperationException($"The IServiceProviderFactory returned a null IServiceProvider.");
    }

    // resolve configuration explicitly once to mark it as resolved within the
    // service provider, ensuring it will be properly disposed with the provider
    _ = _appServices.GetService<IConfiguration>();
}

通过 CreateDefaultBuilder() 构建 HostBuilder 对象实例

那么,这个 HostBuilder 又是什么时候被创建出来的呢?回到我们的主程序,看一看 CreateHostBuilder() 这个方法。

public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });

首先,这里使用了类 Host,并调用了它的静态方法 CreateDefaultBuilder()。注意这个 Host 类的命名空间是:Microsoft.Extensions.Hosting。而以前的 Host 所在的命名空间是 Microsoft.Extensions.Hosting.Internal。我们在 Program.cs 中看到的 Host 是这个静态类。

在 GitHub 中查看 Host 源码

namespace Microsoft.Extensions.Hosting
{
    public static class Host

这个静态类 Host 提供了一个静态方法 CreateDefaultBuilder 来构建 IHostBuilder 实例。可以看到,第一行就是通过 new 操作符创建了 HostBuilder 对象实例。这个时候还没有依赖注入容器。

public static IHostBuilder CreateDefaultBuilder(string[] args)
{
    var builder = new HostBuilder();

    builder.UseContentRoot(Directory.GetCurrentDirectory());
    builder.ConfigureHostConfiguration(config =>
    {
        config.AddEnvironmentVariables(prefix: "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;
}

从源代码中我们可以看到,CreateDefaultBuilder方法配置了默认的ContentRoot配置项,用于指示应用程序从哪里获取静态文件和WEB程序的根目录。加载了DOTNET_前缀的系统环境变量,并从appsettings.jsonappsettings.{env.EnvironmentName}.json文件中加载应用程序配置信息。配置日志组件,并添加了日志系统的输出路径,而且针对Windows操作系统额外添加了日志的拦截器和WindowsEvent组件的日志输出路径。

扩展方法 ConfigureWebHostDefaults

Web 的处理是在哪里配置的呢?

ConfigureWebHostDefaults() 显然是 IHostBuilder 的一个扩展方法,在 GitHub 中查看 GenericHostBuilderExtensions 源码。它的代码并不复杂。

public static class GenericHostBuilderExtensions {
    /// <summary>
    /// Initializes a new instance of the <see cref="IWebHostBuilder"/> class with pre-configured defaults.
    /// </summary>
    /// <remarks>
    ///   The following defaults are applied to the <see cref="IWebHostBuilder"/>:
    ///     use Kestrel as the web server and configure it using the application's configuration providers,
    ///     adds the HostFiltering middleware,
    ///     adds the ForwardedHeaders middleware if ASPNETCORE_FORWARDEDHEADERS_ENABLED=true,
    ///     and enable IIS integration.
    /// </remarks>
    /// <param name="builder">The <see cref="IHostBuilder" /> instance to configure</param>
    /// <param name="configure">The configure callback</param>
    /// <returns>The <see cref="IHostBuilder"/> for chaining.</returns>
    public static IHostBuilder ConfigureWebHostDefaults (this IHostBuilder builder, Action<IWebHostBuilder> configure) {
        return builder.ConfigureWebHost (webHostBuilder => {
            WebHost.ConfigureWebDefaults (webHostBuilder);

            configure (webHostBuilder);
        });
    }
}

这个 ConfigureWebHost 来自 IHostBuilder 的扩展方法,在 GitHub 中查看 GenericHostWebHostBuilderExtensions 源代码

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

可以看到,它内部又调用了 WebHost 中的 ConfigureWebDefaults 方法。

在 GitHub 中查看 WebHost 源代码

internal static void ConfigureWebDefaults (IWebHostBuilder builder) {
    builder.ConfigureAppConfiguration ((Action<WebHostBuilderContext, IConfigurationBuilder>) ((ctx, cb) => {
        if (!ctx.HostingEnvironment.IsDevelopment ())
            return;
        StaticWebAssetsLoader.UseStaticWebAssets (ctx.HostingEnvironment, ctx.Configuration);
    }));
    
    builder.UseKestrel ((Action<WebHostBuilderContext, KestrelServerOptions>) ((builderContext, options) => options.Configure ((IConfiguration) builderContext.Configuration.GetSection ("Kestrel")))).ConfigureServices ((Action<WebHostBuilderContext, IServiceCollection>) ((hostingContext, services) => {
        services.PostConfigure<HostFilteringOptions> ((Action<HostFilteringOptions>) (options => {
            if (options.AllowedHosts != null && options.AllowedHosts.Count != 0)
                return;
            string str = hostingContext.Configuration["AllowedHosts"];
            string[] strArray1;
            if (str == null)
                strArray1 = (string[]) null;
            else
                strArray1 = str.Split (new char[1] { ';' }, StringSplitOptions.RemoveEmptyEntries);
            string[] strArray2 = strArray1;
            HostFilteringOptions filteringOptions = options;
            string[] strArray3;
            if (strArray2 == null || strArray2.Length == 0)
                strArray3 = new string[1] { "*" };
            else
                strArray3 = strArray2;
            filteringOptions.AllowedHosts = (IList<string>) strArray3;
        }));
        services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>> ((IOptionsChangeTokenSource<HostFilteringOptions>) new ConfigurationChangeTokenSource<HostFilteringOptions> (hostingContext.Configuration));
        services.AddTransient<IStartupFilter, HostFilteringStartupFilter> ();
        if (string.Equals ("true", hostingContext.Configuration["ForwardedHeaders_Enabled"], StringComparison.OrdinalIgnoreCase)) {
            services.Configure<ForwardedHeadersOptions> ((Action<ForwardedHeadersOptions>) (options => {
                options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
                options.KnownNetworks.Clear ();
                options.KnownProxies.Clear ();
            }));
            services.AddTransient<IStartupFilter, ForwardedHeadersStartupFilter> ();
        }
        services.AddRouting ();
    })
    .UseIIS ()
    .UseIISIntegration ();
}

从源代码中我们可以看到,ConfigureWebDefaults() 方法配置了 Web Server 组件 Kestrel 用来处理HTTP通信,加载了 CORS 基础配置,配置了 ForwardedHeaders(用于支持代理服务如Nginx等),添加了基础的路由组件并配置了IIS的支持组件。 于是ASP.NET Core应用程序此时已经可以支持 HTTP 通信,并且无需额外配置就可以运行在代理服务器之上。还可以根据请求的URI信息进行路由分配,查找对应的 Controller类/Action方法。

UseStartup

扩展方法 - UseStartup

UseStartup 有一个较长的调用链,最终实际对ASP.NET Core应用程序进行配置的方法是 WebHostBuilderExtensions.UseStartup 方法 WebHostBuilderExtensions.UseStartup。 方法定义在 WebHostBuilderExtensions 源代码

UseStartup 方法

public static IWebHostBuilder UseStartup(
      this IWebHostBuilder hostBuilder,
      Type startupType)
    {
      string name = startupType.GetTypeInfo().Assembly.GetName().Name;
      hostBuilder.UseSetting(WebHostDefaults.ApplicationKey, name);
      return hostBuilder is ISupportsStartup supportsStartup ? supportsStartup.UseStartup(startupType) : hostBuilder.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 =>
          {
            IHostEnvironment requiredService = sp.GetRequiredService<IHostEnvironment>();
            return (object) new ConventionBasedStartup(StartupLoader.LoadMethods(sp, startupType, requiredService.EnvironmentName));
          }));
      }));
    }

UseStartup 方法将会在应用程序中查找注册的 Startup 类,并执行 Startup 类的配置方法,对ASP.NET Core应用程序执行最后的,用户层面的最终配置,在 Startup 类中,我们将详细的配置我们的ASP.NET Core应用程序已满足我们对业务支撑的需要。这也是绝大多数开发者首次接触到的ASP.NET Core应用程序配置的地点。 值得注意的是,在 UseStartup 方法中,会首先查找声明了 IStartup 接口的 Startup 类(如果此处传入的 IWebHostBuiler 声明了 ISupportStartup 接口的话。)。因此除了将 ASP.NET Core 应用程序直接编译成可执行程序外,我们也可以将 ASP.NET Core 应用程序写在类库类型的项目中,由类库的使用者来帮助我们启动 ASP.NET Core应用程序。

终点 - Startup.cs

最后,我们再看一下默认生成的 Startup 类:

namespace SmapleWeb
{
    public class Startup
    {
        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        {
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Hello World!");
                });
            });
        }
    }
}

空 ASP.NET Core项目的 Startup 类非常简单,实现了一个空的 ConfigureServices 方法,在这个方法里,我们可以注入我们需要使用的各种服务类或基础组件类。 并且实现了 Configure() 方法,注册了基础路由组件和终结点组件,能够处理访问 WEB ROOT 的路径的请求,并返回 Hello World!

以上就是 ASP.NET Core应用程序关于HOST类的全部源代码,我们可以看到 ASP.NET Core应用程序的宿主类的实现并不复杂,并且除了支持 ASP.NET Core应用程序之外,还可以支持我们方便的写出各种类型的服务程序,只需要我们根据实际需要实现一些基本类型即可。

posted on 2022-03-05 19:31  冠军  阅读(1357)  评论(0编辑  收藏  举报