什麼是泛型主機
泛型主機(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方法可正常退出。