乘风破浪,遇见最佳跨平台跨终端框架.Net Core/.Net生态 - 泛型主机(Host),封装应用资源和生存期功能
什么是泛型主机
泛型主机(Host
),又名通用主机,是封装应用资源和生存期功能的对象。
一个对象中包含所有应用的相互依赖资源的主要原因是生存期管理:控制应用启动和正常关闭。
其中包括:
- 依赖关系注入(
DI
) - 日志记录(
Logging
) - 应用配置(
Configuration
) - 应用关闭
- 主机服务实现(
IHostedService
)
启动
- 当泛型主机启动时,它将对在托管服务的服务容器集合中注册的
IHostedService
的每个实现调用IHostedService.StartAsync
。 - 在辅助角色服务应用中,包含
BackgroundService
实例的所有IHostedService
实现都调用其BackgroundService.ExecuteAsync
方法。
Nuget包
dotnet add package Microsoft.Extensions.Hosting
基础使用(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.json
和appsettings.{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
,可追加应用配置。
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!");
}
主机关闭
- 控制从
ConsoleLifetime
流向ApplicationLifetime
,引发ApplicationStopping
事件。这会指示WaitForShutdownAsync
解除阻止Main执行代码。同时,由于POSIX信号已经过处理,POSIX信号处理程序返回Cancel=true。 - Main执行代码将再次开始执行,并指示主机
StopAsync()
,进而停止所有托管服务,并引发任何其他已停止的事件。 - 最后,
WaitForShutdown
退出,使任何应用程序清理代码可执行且Main方法可正常退出。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
2021-12-09 乘风破浪,遇见未来元宇宙(Metaverse)之Unity加速布局元宇宙,收购全球顶级特效公司Weta Digital
2021-12-09 乘风破浪,遇见未来元宇宙(Metaverse)之Unity云端分布式算力方案,为大规模实时3D内容需求而生