65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.84/5 (18投票s)

2017 年 9 月 27 日

CPOL

4分钟阅读

viewsIcon

27970

downloadIcon

567

使用 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> 实例包含。

构建主机时,管道如下

  1. IConfigurationBuilder 的处理程序,可以访问 IHostingEnvironment
  2. IConfigurationRoot 的处理程序,可以访问 IHostingEnvironment
  3. ILoggerFactory 的处理程序,可以访问 IConfigurationIHostingEnvironment
  4. IServiceCollection 的处理程序,可以访问 ILoggerFactoryIConfigurationIHostingEnvironment
  5. 构建 IServiceProvider,可以访问 IServiceCollectionILoggerFactoryIConfigurationIHostingEnvironment
  6. IServiceProvider 的处理程序,可以访问 IServiceProviderILoggerFactoryIConfigurationIHostingEnvironment

请注意,ILoggerFactoryIConfigurationRootIHostingEnvironment 实例会自动注册到容器中。

说明:如果您想配置日志工厂、配置或其他任何需求,并且需要了解当前环境或目录,并想确保使用与主机构建器相同的属性,您始终可以使用构造函数或 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 中一样,您可以有条件地加载设置、配置应用程序日志记录或替换默认容器实现。abstractHostStartup 使您只需覆盖设置所需的部分即可。

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 包
© . All rights reserved.