乘风破浪,遇见最佳跨平台跨终端框架.Net Core/.Net生态 - 泛型主机(Host),封装应用资源和生存期功能

什么是泛型主机

泛型主机(Host),又名通用主机,是封装应用资源和生存期功能的对象。

一个对象中包含所有应用的相互依赖资源的主要原因是生存期管理:控制应用启动正常关闭

其中包括:

  • 依赖关系注入(DI)
  • 日志记录(Logging)
  • 应用配置(Configuration)
  • 应用关闭
  • 主机服务实现(IHostedService)

启动

  • 当泛型主机启动时,它将对在托管服务的服务容器集合中注册的IHostedService的每个实现调用IHostedService.StartAsync
  • 在辅助角色服务应用中,包含BackgroundService实例的所有IHostedService实现都调用其BackgroundService.ExecuteAsync方法。

Nuget包

https://www.nuget.org/packages/Microsoft.Extensions.Hosting

dotnet add package Microsoft.Extensions.Hosting

image

基础使用(ConsoleApp)

static void Main(string[] args)
{
    IHost host = Host
    // 创建生成器对象
    .CreateDefaultBuilder(args)
    // 配置生成器对象
    .ConfigureServices((hostContext, services) =>
    {
        services.AddHostedService<AppHostService>();
    })
    // 创建IHost实例
    .Build();
    // 对主机对象调用运行方法
    host.Run();

    Console.WriteLine("Hello, World!");
}

基础使用(WpfApp)

定义应用主机服务AppHostService,它需要继承并实现IHostedService接口

/// <summary>
/// 应用主机服务
/// </summary>
public class AppHostService : IHostedService
{
    /// <summary>
    /// 容器服务提供者
    /// </summary>
    private readonly IServiceProvider _serviceProvider;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="serviceProvider"></param>
    public AppHostService(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    /// <summary>
    /// 启动主机
    /// </summary>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public Task StartAsync(CancellationToken cancellationToken)
    {
        var mainWindow = _serviceProvider.GetService<MainWindow>();
        mainWindow.Show();
        return Task.CompletedTask;
    }

    /// <summary>
    /// 停止主机
    /// </summary>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public Task StopAsync(CancellationToken cancellationToken)
    {
        return Task.CompletedTask;
    }
}

App.xaml.cs的使用示例

public partial class App : Application
{
    /// <summary>
    /// 构建主机对象
    /// </summary>
    private static readonly IHost _host = Host
        // 创建生成器对象
        .CreateDefaultBuilder()
        // 配置生成器对象
        .ConfigureServices((hostContext, services) =>
        {
            // 添加应用主机服务
            services.AddHostedService<AppHostService>();
            // 添加窗体
            services.AddSingleton<MainWindow>();
        })
        // 创建IHost实例
        .Build();

    /// <summary>
    /// 获取应用服务
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public static T GetService<T>() where T : class
    {
        return _host.Services.GetService<T>();
    }

    /// <summary>
    /// 应用启动事件
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private async void OnStartup(object sender, StartupEventArgs e)
    {
        await _host.RunAsync();
    }

    /// <summary>
    /// 应用退出事件
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private async void OnExit(object sender, ExitEventArgs e)
    {
        await _host.StopAsync();
        _host.Dispose();
    }
}

基础使用(WebApi)

.Net Core 3.1

WebApi项目的入口Program.Main,它调用静态方法CreateHostBuilder来创建并配置主机生成器对象。

public class Program
{
    public static void Main(string[] args)
    {
        // 创建并配置主机生成器对象、创建实例、运行实例
        CreateHostBuilder(args).Build().Run();
    }

    /// <summary>
    /// 创建并配置生成器对象
    /// </summary>
    /// <param name="args"></param>
    /// <returns></returns>
    public static IHostBuilder CreateHostBuilder(string[] args) =>
        // 创建生成器对象
        Host.CreateDefaultBuilder(args)
            // 配置Web生成器对象
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

在配置Web生成器对象中使用了Startup这个类,这时候已经可以从容器获取IConfiguration对象了。

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();
    }

    // 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.UseHttpsRedirection();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllers();
        });
    }
}

.Net 6/.Net 7

在.Net 6/.Net 7中已经换成了这样。

public class Program
{
    public static void Main(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);

        // Add services to the container.

        builder.Services.AddControllers();
        // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
        builder.Services.AddEndpointsApiExplorer();
        builder.Services.AddSwaggerGen();

        var app = builder.Build();

        // Configure the HTTP request pipeline.
        if (app.Environment.IsDevelopment())
        {
            app.UseSwagger();
            app.UseSwaggerUI();
        }

        app.UseHttpsRedirection();

        app.UseAuthorization();


        app.MapControllers();

        app.Run();
    }
}

而这里用到的WebApplication是继承自接口IHost的实现。

/// <summary>
/// The web application used to configure the HTTP pipeline, and routes.
/// </summary>
public sealed class WebApplication : IHost, IApplicationBuilder, IEndpointRouteBuilder, IAsyncDisposable
{
    internal const string GlobalEndpointRouteBuilderKey = "__GlobalEndpointRouteBuilder";

    private readonly IHost _host;
    private readonly List<EndpointDataSource> _dataSources = new();

    internal WebApplication(IHost host)
    {
        _host = host;
        ApplicationBuilder = new ApplicationBuilder(host.Services, ServerFeatures);
        Logger = host.Services.GetRequiredService<ILoggerFactory>().CreateLogger(Environment.ApplicationName);

        Properties[GlobalEndpointRouteBuilderKey] = this;
    }

    /// <summary>
    /// The application's configured services.
    /// </summary>
    public IServiceProvider Services => _host.Services;

    /// <summary>
    /// The application's configured <see cref="IConfiguration"/>.
    /// </summary>
    public IConfiguration Configuration => _host.Services.GetRequiredService<IConfiguration>();

    /// <summary>
    /// The application's configured <see cref="IWebHostEnvironment"/>.
    /// </summary>
    public IWebHostEnvironment Environment => _host.Services.GetRequiredService<IWebHostEnvironment>();

    /// <summary>
    /// Allows consumers to be notified of application lifetime events.
    /// </summary>
    public IHostApplicationLifetime Lifetime => _host.Services.GetRequiredService<IHostApplicationLifetime>();

    /// <summary>
    /// The default logger for the application.
    /// </summary>
    public ILogger Logger { get; }

    /// <summary>
    /// The list of URLs that the HTTP server is bound to.
    /// </summary>
    public ICollection<string> Urls => ServerFeatures.Get<IServerAddressesFeature>()?.Addresses ??
        throw new InvalidOperationException($"{nameof(IServerAddressesFeature)} could not be found.");

    IServiceProvider IApplicationBuilder.ApplicationServices
    {
        get => ApplicationBuilder.ApplicationServices;
        set => ApplicationBuilder.ApplicationServices = value;
    }

    internal IFeatureCollection ServerFeatures => _host.Services.GetRequiredService<IServer>().Features;
    IFeatureCollection IApplicationBuilder.ServerFeatures => ServerFeatures;

    internal IDictionary<string, object?> Properties => ApplicationBuilder.Properties;
    IDictionary<string, object?> IApplicationBuilder.Properties => Properties;

    internal ICollection<EndpointDataSource> DataSources => _dataSources;
    ICollection<EndpointDataSource> IEndpointRouteBuilder.DataSources => DataSources;

    internal ApplicationBuilder ApplicationBuilder { get; }

    IServiceProvider IEndpointRouteBuilder.ServiceProvider => Services;

    /// <summary>
    /// Initializes a new instance of the <see cref="WebApplication"/> class with preconfigured defaults.
    /// </summary>
    /// <param name="args">Command line arguments</param>
    /// <returns>The <see cref="WebApplication"/>.</returns>
    public static WebApplication Create(string[]? args = null) =>
        new WebApplicationBuilder(new() { Args = args }).Build();

    /// <summary>
    /// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with preconfigured defaults.
    /// </summary>
    /// <returns>The <see cref="WebApplicationBuilder"/>.</returns>
    public static WebApplicationBuilder CreateBuilder() =>
        new(new());

    /// <summary>
    /// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with preconfigured defaults.
    /// </summary>
    /// <param name="args">Command line arguments</param>
    /// <returns>The <see cref="WebApplicationBuilder"/>.</returns>
    public static WebApplicationBuilder CreateBuilder(string[] args) =>
        new(new() { Args = args });

    /// <summary>
    /// Initializes a new instance of the <see cref="WebApplicationBuilder"/> class with preconfigured defaults.
    /// </summary>
    /// <param name="options">The <see cref="WebApplicationOptions"/> to configure the <see cref="WebApplicationBuilder"/>.</param>
    /// <returns>The <see cref="WebApplicationBuilder"/>.</returns>
    public static WebApplicationBuilder CreateBuilder(WebApplicationOptions options) =>
        new(options);

    /// <summary>
    /// Start the application.
    /// </summary>
    /// <param name="cancellationToken"></param>
    /// <returns>
    /// A <see cref="Task"/> that represents the startup of the <see cref="WebApplication"/>.
    /// Successful completion indicates the HTTP server is ready to accept new requests.
    /// </returns>
    public Task StartAsync(CancellationToken cancellationToken = default) =>
        _host.StartAsync(cancellationToken);

    /// <summary>
    /// Shuts down the application.
    /// </summary>
    /// <param name="cancellationToken"></param>
    /// <returns>
    /// A <see cref="Task"/> that represents the shutdown of the <see cref="WebApplication"/>.
    /// Successful completion indicates that all the HTTP server has stopped.
    /// </returns>
    public Task StopAsync(CancellationToken cancellationToken = default) =>
        _host.StopAsync(cancellationToken);

    /// <summary>
    /// Runs an application and returns a Task that only completes when the token is triggered or shutdown is triggered.
    /// </summary>
    /// <param name="url">The URL to listen to if the server hasn't been configured directly.</param>
    /// <returns>
    /// A <see cref="Task"/> that represents the entire runtime of the <see cref="WebApplication"/> from startup to shutdown.
    /// </returns>
    public Task RunAsync(string? url = null)
    {
        Listen(url);
        return HostingAbstractionsHostExtensions.RunAsync(this);
    }

    /// <summary>
    /// Runs an application and block the calling thread until host shutdown.
    /// </summary>
    /// <param name="url">The URL to listen to if the server hasn't been configured directly.</param>
    public void Run(string? url = null)
    {
        Listen(url);
        HostingAbstractionsHostExtensions.Run(this);
    }

    /// <summary>
    /// Disposes the application.
    /// </summary>
    void IDisposable.Dispose() => _host.Dispose();

    /// <summary>
    /// Disposes the application.
    /// </summary>
    public ValueTask DisposeAsync() => ((IAsyncDisposable)_host).DisposeAsync();

    internal RequestDelegate BuildRequestDelegate() => ApplicationBuilder.Build();
    RequestDelegate IApplicationBuilder.Build() => BuildRequestDelegate();

    // REVIEW: Should this be wrapping another type?
    IApplicationBuilder IApplicationBuilder.New()
    {
        var newBuilder = ApplicationBuilder.New();
        // Remove the route builder so branched pipelines have their own routing world
        newBuilder.Properties.Remove(GlobalEndpointRouteBuilderKey);
        return newBuilder;
    }

    IApplicationBuilder IApplicationBuilder.Use(Func<RequestDelegate, RequestDelegate> middleware)
    {
        ApplicationBuilder.Use(middleware);
        return this;
    }

    IApplicationBuilder IEndpointRouteBuilder.CreateApplicationBuilder() => ((IApplicationBuilder)this).New();

    private void Listen(string? url)
    {
        if (url is null)
        {
            return;
        }

        var addresses = ServerFeatures.Get<IServerAddressesFeature>()?.Addresses;
        if (addresses is null)
        {
            throw new InvalidOperationException($"Changing the URL is not supported because no valid {nameof(IServerAddressesFeature)} was found.");
        }
        if (addresses.IsReadOnly)
        {
            throw new InvalidOperationException($"Changing the URL is not supported because {nameof(IServerAddressesFeature.Addresses)} {nameof(ICollection<string>.IsReadOnly)}.");
        }

        addresses.Clear();
        addresses.Add(url);
    }
}

默认设置

Host名下存在静态方法CreateDefaultBuilder

  • 将内容根目录设置为当前程序运行目录(CreateHostingEnvironment)
  • 添加环境变量和命令行解析(ApplyDefaultHostConfiguration)
  • 应用默认应用配置(ApplyDefaultAppConfiguration)
  • 添加默认服务(AddDefaultServices),含日志服务
  • 开发模式启用范围验证和依赖关系验证(CreateDefaultServiceProviderOptions)

它定义为

public static class Host
{
    [RequiresDynamicCode(RequiresDynamicCodeMessage)]
    public static IHostBuilder CreateDefaultBuilder() =>
        CreateDefaultBuilder(args: null);

    [RequiresDynamicCode(RequiresDynamicCodeMessage)]
    public static IHostBuilder CreateDefaultBuilder(string[]? args)
    {
        HostBuilder builder = new();
        return builder.ConfigureDefaults(args);
    }
}

它的核心是调用另外一个静态扩展方法HostingHostBuilderExtensions.ConfigureDefaults

public static class HostingHostBuilderExtensions
{
    [RequiresDynamicCode(Host.RequiresDynamicCodeMessage)]
    public static IHostBuilder ConfigureDefaults(this IHostBuilder builder, string[]? args)
    {
        return builder.ConfigureHostConfiguration(config => ApplyDefaultHostConfiguration(config, args))
                        .ConfigureAppConfiguration((hostingContext, config) => ApplyDefaultAppConfiguration(hostingContext, config, args))
                        .ConfigureServices(AddDefaultServices)
                        .UseServiceProviderFactory(context => new DefaultServiceProviderFactory(CreateDefaultServiceProviderOptions(context)));
    }
}

将内容根目录设置为当前程序运行目录(CreateHostingEnvironment)

/// <summary>
/// Constants for HostBuilder configuration keys.
/// </summary>
public static class HostDefaults
{
    /// <summary>
    /// The configuration key used to set <see cref="IHostEnvironment.ApplicationName"/>.
    /// </summary>
    public static readonly string ApplicationKey = "applicationName";

    /// <summary>
    /// The configuration key used to set <see cref="IHostEnvironment.EnvironmentName"/>.
    /// </summary>
    public static readonly string EnvironmentKey = "environment";

    /// <summary>
    /// The configuration key used to set <see cref="IHostEnvironment.ContentRootPath"/>
    /// and <see cref="IHostEnvironment.ContentRootFileProvider"/>.
    /// </summary>
    public static readonly string ContentRootKey = "contentRoot";
}

internal static (HostingEnvironment, PhysicalFileProvider) CreateHostingEnvironment(IConfiguration hostConfiguration)
{
    var hostingEnvironment = new HostingEnvironment()
    {
        EnvironmentName = hostConfiguration[HostDefaults.EnvironmentKey] ?? Environments.Production,
        ContentRootPath = ResolveContentRootPath(hostConfiguration[HostDefaults.ContentRootKey], AppContext.BaseDirectory),
    };

    string? applicationName = hostConfiguration[HostDefaults.ApplicationKey];
    if (string.IsNullOrEmpty(applicationName))
    {
        // Note GetEntryAssembly returns null for the net4x console test runner.
        applicationName = Assembly.GetEntryAssembly()?.GetName().Name;
    }

    if (applicationName is not null)
    {
        hostingEnvironment.ApplicationName = applicationName;
    }

    var physicalFileProvider = new PhysicalFileProvider(hostingEnvironment.ContentRootPath);
    hostingEnvironment.ContentRootFileProvider = physicalFileProvider;

    return (hostingEnvironment, physicalFileProvider);
}

internal static string ResolveContentRootPath(string? contentRootPath, string basePath)
{
    if (string.IsNullOrEmpty(contentRootPath))
    {
        return basePath;
    }
    if (Path.IsPathRooted(contentRootPath))
    {
        return contentRootPath;
    }
    return Path.Combine(Path.GetFullPath(basePath), contentRootPath);
}

添加环境变量和命令行解析(ApplyDefaultHostConfiguration)

internal static void ApplyDefaultHostConfiguration(IConfigurationBuilder hostConfigBuilder, string[]? args)
{
    // If we're running anywhere other than C:\Windows\system32, we default to using the CWD for the ContentRoot.
    // However, since many things like Windows services and MSIX installers have C:\Windows\system32 as there CWD which is not likely
    // to really be the home for things like appsettings.json, we skip changing the ContentRoot in that case. The non-"default" initial
    // value for ContentRoot is AppContext.BaseDirectory (e.g. the executable path) which probably makes more sense than the system32.

    // In my testing, both Environment.CurrentDirectory and Environment.GetFolderPath(Environment.SpecialFolder.System) return the path without
    // any trailing directory separator characters. I'm not even sure the casing can ever be different from these APIs, but I think it makes sense to
    // ignore case for Windows path comparisons given the file system is usually (always?) going to be case insensitive for the system path.
    string cwd = Environment.CurrentDirectory;
    if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || !string.Equals(cwd, Environment.GetFolderPath(Environment.SpecialFolder.System), StringComparison.OrdinalIgnoreCase))
    {
        hostConfigBuilder.AddInMemoryCollection(new[]
        {
            new KeyValuePair<string, string?>(HostDefaults.ContentRootKey, cwd),
        });
    }

    hostConfigBuilder.AddEnvironmentVariables(prefix: "DOTNET_");
    if (args is { Length: > 0 })
    {
        hostConfigBuilder.AddCommandLine(args);
    }
}

应用默认应用配置(ApplyDefaultAppConfiguration)

应用默认应用配置(ApplyDefaultAppConfiguration),这里我们看到实际上它将appsettings.jsonappsettings.{env.EnvironmentName}.json两个配置都加载进来了;而且在Development环境下,添加密钥管理器(AddUserSecrets);添加环境变量(AddEnvironmentVariables);添加命令行参数(AddCommandLine)的支持。

internal static void ApplyDefaultAppConfiguration(HostBuilderContext hostingContext, IConfigurationBuilder appConfigBuilder, string[]? args)
{
    IHostEnvironment env = hostingContext.HostingEnvironment;
    bool reloadOnChange = GetReloadConfigOnChangeValue(hostingContext);

    appConfigBuilder.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange);

    if (env.IsDevelopment() && env.ApplicationName is { Length: > 0 })
    {
        var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
        if (appAssembly is not null)
        {
            appConfigBuilder.AddUserSecrets(appAssembly, optional: true, reloadOnChange: reloadOnChange);
        }
    }

    appConfigBuilder.AddEnvironmentVariables();

    if (args is { Length: > 0 })
    {
        appConfigBuilder.AddCommandLine(args);
    }

    [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Calling IConfiguration.GetValue is safe when the T is bool.")]
    static bool GetReloadConfigOnChangeValue(HostBuilderContext hostingContext) => hostingContext.Configuration.GetValue("hostBuilder:reloadConfigOnChange", defaultValue: true);
}

添加默认服务(AddDefaultServices),含日志服务

添加默认服务(AddDefaultServices),这里我们看到添加了日志服务,且日志服务的配置来自Logging节点。这里添加了控制台(AddConsole)、调试(AddDebug)、事件源(AddEventSourceLogger)、事件日志(AddEventLog)日志提供程序。

internal static void AddDefaultServices(HostBuilderContext hostingContext, IServiceCollection services)
{
    services.AddLogging(logging =>
    {
        bool isWindows =
#if NETCOREAPP
            OperatingSystem.IsWindows();
#else
            RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
#endif

        // 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"));
#if NETCOREAPP
        if (!OperatingSystem.IsBrowser())
#endif
        {
            logging.AddConsole();
        }
        logging.AddDebug();
        logging.AddEventSourceLogger();

        if (isWindows)
        {
            // Add the EventLogLoggerProvider on windows machines
            logging.AddEventLog();
        }

        logging.Configure(options =>
        {
            options.ActivityTrackingOptions =
                ActivityTrackingOptions.SpanId |
                ActivityTrackingOptions.TraceId |
                ActivityTrackingOptions.ParentId;
        });
    });
}

开发模式启用范围验证和依赖关系验证(CreateDefaultServiceProviderOptions)

internal static ServiceProviderOptions CreateDefaultServiceProviderOptions(HostBuilderContext context)
{
    bool isDevelopment = context.HostingEnvironment.IsDevelopment();
    return new ServiceProviderOptions
    {
        ValidateScopes = isDevelopment,
        ValidateOnBuild = isDevelopment,
    };
}

内置服务

主机默认注册了以下三个服务

  • 主机应用生命周期(IHostApplicationLifetime)
  • 主机生命周期(IHostLifetime)
  • 主机环境(IHostEnvironment)

主机应用生命周期(IHostApplicationLifetime)

允许通知使用者应用程序生存期事件

  • 在应用程序主机完全启动时触发(ApplicationStarted)
  • 在应用程序主机执行正常关闭时触发(ApplicationStopped)
  • 当应用程序主机执行正常关闭时触发(ApplicationStopping)。在此事件完成之前,关闭将被阻止。

其定义是

/// <summary>
/// Allows consumers to be notified of application lifetime events. This interface is not intended to be user-replaceable.
/// </summary>
public interface IHostApplicationLifetime
{
    /// <summary>
    /// Triggered when the application host has fully started.
    /// </summary>
    CancellationToken ApplicationStarted { get; }

    /// <summary>
    /// Triggered when the application host is starting a graceful shutdown.
    /// Shutdown will block until all callbacks registered on this token have completed.
    /// </summary>
    CancellationToken ApplicationStopping { get; }

    /// <summary>
    /// Triggered when the application host has completed a graceful shutdown.
    /// The application will not exit until all callbacks registered on this token have completed.
    /// </summary>
    CancellationToken ApplicationStopped { get; }

    /// <summary>
    /// Requests termination of the current application.
    /// </summary>
    void StopApplication();
}

使用示例

/// <summary>
/// 应用主机服务
/// </summary>
public class AppHostService : IHostedService
{
    /// <summary>
    /// 容器服务提供者
    /// </summary>
    private readonly IServiceProvider _serviceProvider;

    /// <summary>
    /// 日志服务
    /// </summary>
    private readonly ILogger _logger;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="serviceProvider"></param>
    public AppHostService(ILogger<AppHostService> logger, IServiceProvider serviceProvider, IHostApplicationLifetime hostApplicationLifetime)
    {
        _logger = logger;
        _serviceProvider = serviceProvider;

        // 注册应用已启动事件处理方法
        hostApplicationLifetime.ApplicationStarted.Register(OnStarted);
        // 注册应用正在停止事件处理方法
        hostApplicationLifetime.ApplicationStopping.Register(OnStopping);
        // 注册应用已停止事件处理方法
        hostApplicationLifetime.ApplicationStopped.Register(OnStopped);
        // 可选调用停止应用方法
        //hostApplicationLifetime.StopApplication();
    }

    /// <summary>
    /// 应用已启动
    /// </summary>
    private void OnStarted()
    {
        _logger.LogInformation("App Started");
    }

    /// <summary>
    /// 应用正在停止
    /// </summary>
    private void OnStopping()
    {
        _logger.LogInformation("App Stopping");
    }

    /// <summary>
    /// 应用已停止
    /// </summary>
    private void OnStopped()
    {
        _logger.LogInformation("App Stopped");
    }

    /// <summary>
    /// 启动主机
    /// </summary>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Host Start");
        var mainWindow = _serviceProvider.GetService<MainWindow>();
        mainWindow.Show();
        return Task.CompletedTask;
    }

    /// <summary>
    /// 停止主机
    /// </summary>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Host Stop");
        return Task.CompletedTask;
    }
}

输出

demoForHostWpf60.AppHostService: Information: Host Start
demoForHostWpf60.AppHostService: Information: App Started
Microsoft.Hosting.Lifetime: Information: Application started. Press Ctrl+C to shut down.
Microsoft.Hosting.Lifetime: Information: Hosting environment: Production
Microsoft.Hosting.Lifetime: Information: Content root path: demoForHostApp60\bin\Debug\net6.0-windows
demoForHostWpf60.AppHostService: Information: App Stopping
Microsoft.Hosting.Lifetime: Information: Application is shutting down...
demoForHostWpf60.AppHostService: Information: Host Stop
demoForHostWpf60.AppHostService: Information: App Stopped

主机生命周期(IHostLifetime)

主机生命周期(IHostLifetime)用于控制主机何时启动、何时停止。

  • 指示主机正在停止并且可以关闭(StopAsync(CancellationToken))
  • StartAsync(CancellationToken)开始时调用,在继续之前,会一直等待该操作完成。它可用于延迟启动,直到外部事件发出信号(WaitForStartAsync(CancellationToken))。

其定义是

public interface IHostLifetime
{
    /// <summary>
    /// Called at the start of <see cref="IHost.StartAsync(CancellationToken)"/> which will wait until it's complete before
    /// continuing. This can be used to delay startup until signaled by an external event.
    /// </summary>
    /// <param name="cancellationToken">Used to abort program start.</param>
    /// <returns>A <see cref="Task"/>.</returns>
    Task WaitForStartAsync(CancellationToken cancellationToken);

    /// <summary>
    /// Called from <see cref="IHost.StopAsync(CancellationToken)"/> to indicate that the host is stopping and it's time to shut down.
    /// </summary>
    /// <param name="cancellationToken">Used to indicate when stop should no longer be graceful.</param>
    /// <returns>A <see cref="Task"/>.</returns>
    Task StopAsync(CancellationToken cancellationToken);
}

它将使用注册的最后一个实现,但是默认实现是Microsoft.Extensions.Hosting.Internal.ConsoleLifetime,我们可以看下ConsoleLifetime的定义。

/// <summary>
/// Listens for Ctrl+C or SIGTERM and initiates shutdown.
/// </summary>
[UnsupportedOSPlatform("android")]
[UnsupportedOSPlatform("browser")]
[UnsupportedOSPlatform("ios")]
[UnsupportedOSPlatform("tvos")]
public partial class ConsoleLifetime : IHostLifetime, IDisposable
{
    private CancellationTokenRegistration _applicationStartedRegistration;
    private CancellationTokenRegistration _applicationStoppingRegistration;

    public ConsoleLifetime(IOptions<ConsoleLifetimeOptions> options, IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, IOptions<HostOptions> hostOptions)
        : this(options, environment, applicationLifetime, hostOptions, NullLoggerFactory.Instance) { }

    public ConsoleLifetime(IOptions<ConsoleLifetimeOptions> options, IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, IOptions<HostOptions> hostOptions, ILoggerFactory loggerFactory)
    {
        ThrowHelper.ThrowIfNull(options?.Value, nameof(options));
        ThrowHelper.ThrowIfNull(applicationLifetime);
        ThrowHelper.ThrowIfNull(environment);
        ThrowHelper.ThrowIfNull(hostOptions?.Value, nameof(hostOptions));

        Options = options.Value;
        Environment = environment;
        ApplicationLifetime = applicationLifetime;
        HostOptions = hostOptions.Value;
        Logger = loggerFactory.CreateLogger("Microsoft.Hosting.Lifetime");
    }

    private ConsoleLifetimeOptions Options { get; }

    private IHostEnvironment Environment { get; }

    private IHostApplicationLifetime ApplicationLifetime { get; }

    private HostOptions HostOptions { get; }

    private ILogger Logger { get; }

    public Task WaitForStartAsync(CancellationToken cancellationToken)
    {
        if (!Options.SuppressStatusMessages)
        {
            _applicationStartedRegistration = ApplicationLifetime.ApplicationStarted.Register(state =>
            {
                ((ConsoleLifetime)state!).OnApplicationStarted();
            },
            this);
            _applicationStoppingRegistration = ApplicationLifetime.ApplicationStopping.Register(state =>
            {
                ((ConsoleLifetime)state!).OnApplicationStopping();
            },
            this);
        }

        RegisterShutdownHandlers();

        // Console applications start immediately.
        return Task.CompletedTask;
    }

    private partial void RegisterShutdownHandlers();

    private void OnApplicationStarted()
    {
        Logger.LogInformation("Application started. Press Ctrl+C to shut down.");
        Logger.LogInformation("Hosting environment: {EnvName}", Environment.EnvironmentName);
        Logger.LogInformation("Content root path: {ContentRoot}", Environment.ContentRootPath);
    }

    private void OnApplicationStopping()
    {
        Logger.LogInformation("Application is shutting down...");
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        // There's nothing to do here
        return Task.CompletedTask;
    }

    public void Dispose()
    {
        UnregisterShutdownHandlers();

        _applicationStartedRegistration.Dispose();
        _applicationStoppingRegistration.Dispose();
    }

    private partial void UnregisterShutdownHandlers();
}

这里我们看到,其实前面我们看到的一些默认输出,其实就是它做的。

它这里也是注册并监听了应用已启动事件,在OnApplicationStarted中写了一些日志

Logger.LogInformation("Application started. Press Ctrl+C to shut down.");
Logger.LogInformation("Hosting environment: {EnvName}", Environment.EnvironmentName);
Logger.LogInformation("Content root path: {ContentRoot}", Environment.ContentRootPath);

它注册并监听了应用即将停止事件,在OnApplicationStopping中写了一些日志

Logger.LogInformation("Application is shutting down...");

而且它的日志标记叫Microsoft.Hosting.Lifetime

public partial class ConsoleLifetime : IHostLifetime, IDisposable
{

    public ConsoleLifetime(IOptions<ConsoleLifetimeOptions> options, IHostEnvironment environment, IHostApplicationLifetime applicationLifetime, IOptions<HostOptions> hostOptions, ILoggerFactory loggerFactory)
    {
        ThrowHelper.ThrowIfNull(options?.Value, nameof(options));
        ThrowHelper.ThrowIfNull(applicationLifetime);
        ThrowHelper.ThrowIfNull(environment);
        ThrowHelper.ThrowIfNull(hostOptions?.Value, nameof(hostOptions));

        Options = options.Value;
        Environment = environment;
        ApplicationLifetime = applicationLifetime;
        HostOptions = hostOptions.Value;
        Logger = loggerFactory.CreateLogger("Microsoft.Hosting.Lifetime");
    }
}

所以最终输出才是

Microsoft.Hosting.Lifetime: Information: Application started. Press Ctrl+C to shut down.
Microsoft.Hosting.Lifetime: Information: Hosting environment: Production
Microsoft.Hosting.Lifetime: Information: Content root path: demoForHostApp60\bin\Debug\net6.0-windows
Microsoft.Hosting.Lifetime: Information: Application is shutting down...

主机环境(IHostEnvironment)

提供有关其中正在运行应用程序的宿主环境的信息

主机环境(IHostEnvironment)可用于获取环境设置信息。

  • 获取或设置应用程序的名称(ApplicationName), 主机自动将此属性设置为包含应用程序入口点的程序集。
  • 获取或设置环境的名称(EnvironmentName), 主机自动将此属性设置为配置中指定的"环境"键的值。
  • 获取或设置包含应用程序内容文件的目录的绝对路径(ContentRootPath)。
  • 获取或设置指向ContentRootPath的IFileProvider(ContentRootFileProvider)。

其定义如下

/// <summary>
/// Provides information about the hosting environment an application is running in.
/// </summary>
public interface IHostEnvironment
{
    /// <summary>
    /// Gets or sets the name of the environment. The host automatically sets this property to the value of the
    /// "environment" key as specified in configuration.
    /// </summary>
    string EnvironmentName { get; set; }

    /// <summary>
    /// Gets or sets the name of the application. This property is automatically set by the host to the assembly containing
    /// the application entry point.
    /// </summary>
    string ApplicationName { get; set; }

    /// <summary>
    /// Gets or sets the absolute path to the directory that contains the application content files.
    /// </summary>
    string ContentRootPath { get; set; }

    /// <summary>
    /// Gets or sets an <see cref="IFileProvider"/> pointing at <see cref="ContentRootPath"/>.
    /// </summary>
    IFileProvider ContentRootFileProvider { get; set; }
}

我们来看看它的默认实现Microsoft.Extensions.Hosting.Internal.HostingEnvironment

public class HostingEnvironment : IHostingEnvironment, IHostEnvironment
{
    public string EnvironmentName { get; set; } = string.Empty;

    public string ApplicationName { get; set; } = string.Empty;

    public string ContentRootPath { get; set; } = string.Empty;

    public IFileProvider ContentRootFileProvider { get; set; } = null!;
}

使用示例

/// <summary>
/// 构造函数
/// </summary>
public AppHostService(ILogger<AppHostService> logger, IHostEnvironment hostEnvironment)
{
    _logger = logger;

    // 应用当前设置环境
    _logger.LogInformation("App EnvironmentName:{0}", hostEnvironment.EnvironmentName);
    // 应用当前设置名称
    _logger.LogInformation("App ApplicationName:{0}", hostEnvironment.ApplicationName);
    // 应用当前文件根目录
    _logger.LogInformation("App ContentRootPath:{0}", hostEnvironment.ContentRootPath);
    // 应用当前文件提供方
    _logger.LogInformation("App ContentRootFileProvider:{0}", hostEnvironment.ContentRootFileProvider);
}

输出结果

demoForHostWpf60.AppHostService: Information: App EnvironmentName:Production
demoForHostWpf60.AppHostService: Information: App ApplicationName:demoForHostWpf60
demoForHostWpf60.AppHostService: Information: App ContentRootPath:\demoForHostApp60\bin\Debug\net6.0-windows
demoForHostWpf60.AppHostService: Information: App ContentRootFileProvider:Microsoft.Extensions.FileProviders.PhysicalFileProvider

主机配置

在创建默认生成器对象之后将得到一个IHostBuilder对象,基于它存在扩展方法ConfigureHostConfiguration,可追加主机配置。

static void Main(string[] args)
{
    IHost host = Host
    // 创建生成器对象
    .CreateDefaultBuilder(args)
    // 追加主机配置
    .ConfigureHostConfiguration(configHost =>
    {
        configHost.SetBasePath(Directory.GetCurrentDirectory());
        configHost.AddJsonFile("hostsettings.json", optional: true);
        configHost.AddEnvironmentVariables(prefix: "PREFIX_");
        configHost.AddCommandLine(args);
    })
    // 配置生成器对象
    .ConfigureServices((hostContext, services) =>
    {
        services.AddHostedService<AppHostService>();
    })
    // 创建IHost实例
    .Build();
    // 对主机对象调用运行方法
    host.Run();

    Console.WriteLine("Hello, World!");
}

应用配置

在创建默认生成器对象之后将得到一个IHostBuilder对象,基于它存在扩展方法ConfigureAppConfiguration,可追加应用配置。

image

static void Main(string[] args)
{
    IHost host = Host
    // 创建生成器对象
    .CreateDefaultBuilder(args)
    // 追加应用配置
    .ConfigureAppConfiguration(configApp =>
    {
        configApp.SetBasePath(Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location));
    })
    // 配置生成器对象
    .ConfigureServices((hostContext, services) =>
    {
        services.AddHostedService<AppHostService>();
    })
    // 创建IHost实例
    .Build();
    // 对主机对象调用运行方法
    host.Run();

    Console.WriteLine("Hello, World!");
}

主机关闭

image

  1. 控制从ConsoleLifetime流向ApplicationLifetime,引发ApplicationStopping事件。这会指示WaitForShutdownAsync解除阻止Main执行代码。同时,由于POSIX信号已经过处理,POSIX信号处理程序返回Cancel=true。
  2. Main执行代码将再次开始执行,并指示主机StopAsync(),进而停止所有托管服务,并引发任何其他已停止的事件。
  3. 最后,WaitForShutdown退出,使任何应用程序清理代码可执行且Main方法可正常退出。

参考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章