• .NET CORE编写控制台程序应有的优雅姿势(转载)


    原文地址:https://www.cnblogs.com/zuowj/p/11107243.html

    本文所说的编写控制台程序应有的“正确”方法,我把正确二字加上引号,因为没有绝对的正确,因人而异,因系统设计需求而异,我这里所谓的正确方法是指使用面向对象,依赖注入IOC,切面控制AOP等编码规范来提升程序的性能、整洁度、可读性、可维护性等,最终达到让人感觉有点高大上,有点优雅的样子

    先来说说.NET CORE编写控制台程序,目前网络上大把的讲解ASP.NET CORE的编写规范,反而对于.NET CORE控制台程序编写规范介绍比较少,大多停留在Hello Word 程序中,而本文则来讲讲.NET CORE控制台的编写规范(应有的优雅姿势)^ v ^

     如果说不讲什么IOC,DI,AOP等,不讲扩展性,规范性,全部面向过程(方法)编程,那估计没什么好讲的,因为无非就是定义一个class,然后在class中定义一堆的method(方法),如果在方法中需要使用到其它第三方组件,则直接单独引用,引用后进行简单封装util工具类的静态方法,甚至也不用封装,直接使用原生的方法,总之全部都是方法调方法。而这里所演示的编写控制台方法均是尽可能的使用.NET CORE所具有的特性,只有这样才能体现出.NET CORE框架的优势,否则普通控制台程序与.NET CORE控制台程序有什么区别。

    编写.NET CORE控制台程序优雅姿势一:(直接使用.NET CORE的 IOC、Logging、Config组件)

    代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    //Program.cs
     
    using Microsoft.Extensions.DependencyInjection;
    using System;
    using Microsoft.Extensions.Logging;
    using Microsoft.Extensions.Configuration.Json;
    using Microsoft.Extensions.Configuration;
    using System.IO;
     
    namespace NetCoreConsoleApp
    {
        class Program
        {
            static void Main(string[] args)
            {
                //设置config文件
                var config = new ConfigurationBuilder()
                                    .SetBasePath(Directory.GetCurrentDirectory())
                                    .AddJsonFile("appSettings.json", optional: true, reloadOnChange: true).Build();
     
                //设置依赖注入
                var provider = new ServiceCollection()
                                        .AddLogging(configLogging => //设置日志组件
                                        {
                                            configLogging.SetMinimumLevel(LogLevel.Information);
                                            configLogging.AddConsole();
                                        })
                                       .AddScoped<IConfiguration>(p => config)
                                       .AddScoped<HostService>()
                                       .BuildServiceProvider();
     
                var hostService = provider.GetService<HostService>();
     
                hostService.RunAsync();//统一入口服务
     
                Console.WriteLine("提示:程序已正常启动运行,按任意键停止运行并关闭程序...");
                Console.ReadLine();
     
            }
        }
    }
     
     
    //HostService.cs<br>
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Logging;
    using System;
    using System.Diagnostics;
    using System.Threading;
    using System.Threading.Tasks;
     
    namespace NetCoreConsoleApp
    {
        public class HostService
        {
            private readonly IConfiguration config;
            private readonly ILogger<HostService> logger;
            public HostService(IConfiguration config, ILogger<HostService> logger)
            {
                this.config = config;
                this.logger = logger;
            }
     
            public void RunAsync()
            {
                Task.Run((Action)Execute);
            }
     
            /// <summary>
            /// 控制台核心执行入口方法
            /// </summary>
            private void Execute()
            {
                //TODO 业务逻辑代码,如下模拟
                Stopwatch stopwatch = Stopwatch.StartNew();
                for (int i = 1; i <= 100; i++)
                {
                    Console.WriteLine("test WriteLine:" + i);
                    Thread.Sleep(100);
                }
     
                stopwatch.Stop();
     
                logger.LogInformation("Logging - Execute Elapsed Times:{}ms", stopwatch.ElapsedMilliseconds);
            }
     
        }
    }

    因为要使用.NET CORE相关核心组件,故需要引用相关的NuGet包(引用包的方式有多种方式),而且默认的.NET CORE控制台只会生成DLL并不会生成EXE启动程序,故如果仅在WIN系统下使用,还需要设置生成方式等,详细配置属性如下:(项目文件csproj)

    复制代码
    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>netcoreapp2.2</TargetFramework>
        <RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
        <SelfContained>false</SelfContained>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
        <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
        <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
      </ItemGroup>
    
    </Project>
    复制代码

     如上代码虽简单但代码编写顺序很关键,这里进行说明一下:

    1.因为一般应用程序都会有config文件,故我们需要先通过new ConfigurationBuilder来设置config文件的方式及路径;

    2.因为要使用.NET CORE默认的IOC框架,故new ServiceCollection,然后将相关的依赖服务组件注册到IOC容器中;

    3.config、logging 均是一个程序最基本的依赖组件,故将其注册到IOC容器中,注册logging有专门的扩展方法(AddLogging),而config没有则直接使用通过的注册方法(当然也可以基于ServiceCollection写一个AddConfiguration扩展方法)

    4.控制台需要一个核心的入口方法,用于处理核心业务,不要直接在Program中写方法,这样就不能使用IOC,同时也没有做到职责分明,Program仅是程序启动入口,业务处理应该有专门的入口,故上述代码中有HostService类(即:核心宿主服务类, 意为存在于控制台中的服务处理类,在这个类的构造涵数中列出所需依赖的服务组件,以便实例化时IOC可以自动注入这个参数),并注册到IOC容器中,当然也可以先定义一个IHostService接口然后实现这个接口。(如果有多个HostService类实例,建议定义一个IHostService接口,接口中只需要入口方法定义即可,如:RunAsync)

    5.当各组件初始化设置OK、IOC注册到位后,就应该通过IOC解析获得HostService类实例,并执行入口方法:RunAsync,该方法为异步后台执行,即调用该方法后,会在单独的后台线程处理核心业务,然后主线程继续往下面走,输出关闭提示信息,最后的Console.ReadLine();很关键,这个是等待输入流并挂起当前主线程,目的大家都知道,不要让控制台程序关闭。

     通过上述的讲解及源代码展示,有没有感觉优雅呢?如果觉得这样还算优雅,那下面展示的第二种更优雅的姿势

    编写.NET CORE控制台程序优雅姿势二:(使用通用主机也称泛型主机HostBuilder)

    代码如下:Program.cs

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Logging;
    using NLog.Extensions.Logging;
    using Microsoft.Extensions.Configuration;
    using System.IO;
    using Polly;
    using System;
     
    namespace NetCoreConsoleApp
    {
        class Program
        {
            static void Main(string[] args)
            {
                var host = new HostBuilder()
                    .ConfigureHostConfiguration(configHost =>
                    {
                        configHost.SetBasePath(Directory.GetCurrentDirectory());
                    })
                    .ConfigureAppConfiguration(configApp =>
                    {
                        configApp.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
                    })
                    .ConfigureServices((context, services) =>
                    {
                        //添加数据访问组件示例:services.AddTransient<IDbAccesser>(provider =>
                        //{
                        //    string connStr = context.Configuration.GetConnectionString("ConnDbStr");
                        //    return new SqlDapperEasyUtil(connStr);
                        //});
     
                        //添加HttpClient封装类示例:services.AddHttpClient<GitHubApiClient>()
                        //.AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync(3, t => TimeSpan.FromMilliseconds(800)));
     
                        services.AddHostedService<DemoHostedService>();
                    })
                    .ConfigureLogging((context, configLogging) =>
                    {
                        configLogging.ClearProviders();
                        configLogging.SetMinimumLevel(LogLevel.Trace);
                        configLogging.AddNLog(context.Configuration);
                    })
                    .UseConsoleLifetime()
                    .Build();
     
                host.Run();
            }
        }
    }

    DemoHostedService类代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Logging;
    using System;
    using System.Diagnostics;
    using System.Threading;
    using System.Threading.Tasks;
     
    namespace NetCoreConsoleApp
    {
        public class DemoHostedService : IHostedService
        {
            private readonly IConfiguration config;
            private readonly ILogger logger;
     
            public DemoHostedService(IConfiguration config, ILogger<DemoHostedService> logger)
            {
                this.config = config;
                this.logger = logger;
            }
     
            public Task StartAsync(CancellationToken cancellationToken)
            {
                Console.WriteLine(nameof(DemoHostedService) + "已开始执行...");
     
                //TODO 业务逻辑代码,如下模拟
                Stopwatch stopwatch = Stopwatch.StartNew();
                for (int i = 1; i <= 100; i++)
                {
                    Console.WriteLine("test WriteLine:" + i);
                    Thread.Sleep(100);
                }
     
                stopwatch.Stop();
     
                logger.LogInformation("Logging - Execute Elapsed Times:{}ms", stopwatch.ElapsedMilliseconds);
     
                return Task.FromResult(0);
            }
     
            public Task StopAsync(CancellationToken cancellationToken)
            {
                Console.WriteLine(nameof(DemoHostedService) + "已被停止");
                return Task.FromResult(0);
            }
     
        }
    }

     

    因为要使用HostBuilder类及相关的.NET CORE组件(如上代码主要使用到了:Host、Dapper、Nlog、Polly等),故仍需引用相关的NuGet包,详细配置属性如下:(项目文件csproj)

    复制代码
    <Project Sdk="Microsoft.NET.Sdk">
    
      <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>netcoreapp2.2</TargetFramework>
        <RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
        <SelfContained>false</SelfContained>
      </PropertyGroup>
    
      <ItemGroup>
        <PackageReference Include="Dapper" Version="1.60.6" />
        <PackageReference Include="Microsoft.Extensions.Configuration.FileExtensions" Version="2.2.0" />
        <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.2.0" />
        <PackageReference Include="Microsoft.Extensions.Hosting" Version="2.2.0" />
        <PackageReference Include="Microsoft.Extensions.Http.Polly" Version="2.2.0" />
        <PackageReference Include="NLog.Extensions.Logging" Version="1.5.1" />
        <PackageReference Include="System.Collections.Concurrent" Version="4.3.0" />
      </ItemGroup>
    
      <ItemGroup>
        <None Update="appsettings.json">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </None>
        <None Update="nlog.config">
          <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
        </None>
      </ItemGroup>
    
    </Project>
    复制代码

     如上代码所示,写过ASP.NET CORE程序的人可能比较眼熟,这与ASP.NET CORE的写法很类似,是的,你没有看错,HostBuilder是通用主机,是可以广泛应用于非HTTP的环境下,而ASP.NET CORE中的WebHostBuilder 主要用于HTTP WEB环境,使用方式基本类似,都是先定义HostBuilder,然后利用扩展方法注册、配置各种组件(中间件),最后调用Host的Run方法,开启后台服务执行,不同的是WebHostBuilder多了属于HTTP专有的一些属性及方法及其适用的中间件。

    由于这种写法比较通用,适用于已熟悉.NET CORE或ASP.NET CORE的人群,上手也较简单,故建议采取这种方式来写.NET CORE控制台程序。需要注意的是HostBuilder中最重要的是:注册HostedService 服务,如上代码中的DemoHostedService即是实现了IHostedService接口的宿主后台服务类,可以定义多个,然后都注册到IOC中,最后Host会按注册先后顺序执行多个HostedService服务的StartAsync方法,当停止时同样会执行多个HostedService服务的StopAsync方法

  • 相关阅读:
    32/64位平台printf uint64的方法
    [Linux 安装]centos安装vmware tools
    【Linux】fg bg ctrl + z jobs & 等命令
    让我们慢慢地彼此遗忘
    ctrl+c,ctrl+d,ctrl+z在linux中意义
    Windows 7 右键菜单 添加命令
    修改vs2010所有工程的include目录和lib目录
    Vmware虚拟机的linux与主机共享
    防止atoi函数内存越界
    JavaScriptSerializer使用条件
  • 原文地址:https://www.cnblogs.com/huangzelin/p/11128346.html
Copyright © 2020-2023  润新知