在 .NET 中创建高级控制台应用程序






4.84/5 (18投票s)
使用 SimpleSoft.Hosting 包为控制台应用程序添加依赖注入、日志记录和配置支持
引言
在本文中,我将通过一个示例,介绍如何使用 SimpleSoft.Hosting 包实现更高级的控制台应用程序。其理念是通过利用最新的 Microsoft.Extensions.*
包,更轻松地设置依赖注入、日志记录和导入应用程序设置,而无需过多样板代码。这些概念与 Microsoft.AspNetCore.Hosting
包类似,但省去了不必要的 ASP.NET 依赖项,对于创建 Windows 服务、实用工具、自托管 WCF 应用程序或其他您可能有的需求非常有用。
在此示例中,该应用程序是一个简单的控制台应用程序,用于执行一些定期的 Web 请求。地址和批处理延迟从应用程序设置中读取,NLog 用于将日志写入文件,AutoFac 是依赖注入容器。
Using the Code
该应用程序主要分布在三个类中
Program
- 控制台应用程序的入口点HttpMonitorStartup
- 注册和配置所有必需依赖项的配置类HttpMonitorHost
- 主机入口点类
程序
由于这是一个控制台应用程序,我将首先解释入口点类
public class Program
{
private static readonly CancellationTokenSource TokenSource;
static Program()
{
TokenSource = new CancellationTokenSource();
Console.CancelKeyPress += (sender, args) =>
{
TokenSource.Cancel();
args.Cancel = true;
};
}
public static int Main(string[] args) =>
MainAsync(args, TokenSource.Token).ConfigureAwait(false).GetAwaiter().GetResult();
private static async Task<int> MainAsync(string[] args, CancellationToken ct)
{
ExecutionResult result;
var loggerFactory = new LoggerFactory()
.AddConsole(LogLevel.Trace, true);
var logger = loggerFactory.CreateLogger<Program>();
try
{
logger.LogDebug("Preparing the host builder");
using (var hostBuilder = new HostBuilder("HTTPMONITOR_ENVIRONMENT")
.UseLoggerFactory(loggerFactory)
.UseStartup<HttpMonitorStartup>()
.ConfigureConfigurationBuilder(p =>
{
p.Builder.AddCommandLine(args);
}))
{
await hostBuilder.RunHostAsync<HttpMonitorHost>(ct);
}
result = ExecutionResult.Success;
}
catch (TaskCanceledException)
{
logger.LogWarning("The application execution was canceled");
result = ExecutionResult.Canceled;
}
catch (Exception e)
{
logger.LogCritical(0, e, "Unexpected exception has occurred");
result = ExecutionResult.Failed;
}
logger.LogInformation("Application terminated [{result}]. Press <enter> to exit...", result);
Console.ReadLine();
return (int) result;
}
private enum ExecutionResult
{
Success = 0,
Failed = 1,
Canceled = 2
}
}
ILoggerFactory
最初在构建器外部创建,因为我希望至少将所有日志包含在控制台中。这不是必需的,因为如果没有设置,构建器将在构建主机时使用一个没有任何提供程序的默认工厂。我还使用了环境变量 HTTPMONITOR_ENVIRONMENT
来决定主机在哪个环境中运行。
在此示例中,我使用了一个自定义的 IHostStartup
类来注册和配置所有应用程序依赖项。有方法可以直接将任何支持的配置处理程序添加到构建器中,但我更喜欢将代码保持在主方法中保持简洁。请注意,我附加了第二个 IConfigurationBuilder
处理程序以将参数包含到配置中,但我也可以轻松地将它们作为参数传递给启动类。
然后,我只需运行主机。如果该类没有手动注册到服务集合中,构建器会自动以作用域生命周期添加它,因此它将被 IHostRunContext<THost>
实例包含。
构建主机时,管道如下
IConfigurationBuilder
的处理程序,可以访问IHostingEnvironment
IConfigurationRoot
的处理程序,可以访问IHostingEnvironment
ILoggerFactory
的处理程序,可以访问IConfiguration
和IHostingEnvironment
IServiceCollection
的处理程序,可以访问ILoggerFactory
、IConfiguration
和IHostingEnvironment
- 构建
IServiceProvider
,可以访问IServiceCollection
、ILoggerFactory
、IConfiguration
和IHostingEnvironment
IServiceProvider
的处理程序,可以访问IServiceProvider
、ILoggerFactory
、IConfiguration
和IHostingEnvironment
请注意,ILoggerFactory
、IConfigurationRoot
和 IHostingEnvironment
实例会自动注册到容器中。
说明:如果您想配置日志工厂、配置或其他任何需求,并且需要了解当前环境或目录,并想确保使用与主机构建器相同的属性,您始终可以使用构造函数或 static
方法 HostingEnvironment.BuildDefault
创建 HostingEnvironment
实例,并将环境作为参数传递给构建器。示例
var loggerFactory = new LoggerFactory()
.AddConsole(LogLevel.Trace, true)
.AddNLog();
var env = HostingEnvironment.BuildDefault("HTTPMONITOR_ENVIRONMENT");
var nlogConfigFile = env.ContentRootFileProvider.GetFileInfo($"nlog.{env.Name}.config");
if (!nlogConfigFile.Exists)
nlogConfigFile = env.ContentRootFileProvider.GetFileInfo("nlog.config");
loggerFactory.ConfigureNLog(nlogConfigFile.PhysicalPath);
var builder = new HostBuilder(env).UseLoggerFactory(loggerFactory);
HttpMonitorStartup
尽管不需要,但 startup
类可用于聚合所有主机设置
public class HttpMonitorStartup : HostStartup
{
public override void ConfigureConfigurationBuilder(IConfigurationBuilderParam param)
{
param.Builder
.SetBasePath(param.Environment.ContentRootPath)
.AddJsonFile("appsettings.json", false, true)
.AddJsonFile($"appsettings.{param.Environment.Name}.json", true, true)
.AddEnvironmentVariables();
}
public override void ConfigureLoggerFactory(ILoggerFactoryHandlerParam param)
{
var nlogConfigFile = param.Environment.ContentRootFileProvider.GetFileInfo
($"nlog.{param.Environment.Name}.config");
if (!nlogConfigFile.Exists)
nlogConfigFile = param.Environment.ContentRootFileProvider.GetFileInfo("nlog.config");
param.LoggerFactory
.AddNLog()
.ConfigureNLog(nlogConfigFile.PhysicalPath);
}
public override void ConfigureServiceCollection(IServiceCollectionHandlerParam param)
{
param.ServiceCollection
.AddOptions()
.Configure<HttpMonitorOptions>(param.Configuration)
.AddSingleton(s => s.GetRequiredService<IOptions<HttpMonitorOptions>>().Value);
param.ServiceCollection
.AddSingleton(s => new HttpClient())
.AddSingleton<IUrlRequester, UrlRequester>();
}
public override IServiceProvider BuildServiceProvider(IServiceProviderBuilderParam param)
{
var container = new ContainerBuilder();
container.Populate(param.ServiceCollection);
return new AutofacServiceProvider(container.Build());
}
}
就像在 ASP.NET Core 中一样,您可以有条件地加载设置、配置应用程序日志记录或替换默认容器实现。abstract
类 HostStartup
使您只需覆盖设置所需的部分即可。
HttpMonitorHost
应用程序主机运行在依赖注入容器的作用域内,这意味着您现在拥有了与任何 ASP.NET 应用程序一样完善的设置。在这种情况下,我只需获取应用程序选项并开始进行 HTTP 请求。
public class HttpMonitorHost : IHost
{
private readonly HttpMonitorOptions _options;
private readonly IUrlRequester _urlRequester;
private readonly ILogger<HttpMonitorHost> _logger;
public HttpMonitorHost
(HttpMonitorOptions options, IUrlRequester urlRequester, ILogger<HttpMonitorHost> logger)
{
_options = options;
_urlRequester = urlRequester;
_logger = logger;
}
public async Task RunAsync(CancellationToken ct)
{
for (var i = 0; i < _options.MaximumBatches; i++)
{
_logger.LogDebug("Sending a batch of requests");
await Task.WhenAll(_options.Urls.Select(url => _urlRequester.RequestAsync(url, ct)));
_logger.LogDebug("Waiting for the next batch time");
await Task.Delay(_options.BatchDelayInMs, ct);
}
_logger.LogDebug
("Maximum of {maximumBatches} batches have been made", _options.MaximumBatches);
}
}
执行
运行应用程序时,您应该会看到类似以下的输出
结论
本文介绍了如何使用 SimpleSoft.Hosting 包开发高级控制台应用程序。由于它使用了最新的 Microsoft 扩展包,因此第三方支持非常出色,是一个值得考虑的选项。
历史
- 2017-09-27:初始版本
- 2017-09-29:添加了 VS 2015 项目,并添加了关于如何在创建构建器之前使用
HostingEnvironment
的说明 - 2017-10-03:添加了示例图片,并将代码更新至最新的 NuGet 包