我首选的.NET控制台堆栈

2021-01-16 00:28:39

自从我在80年代中期开始学习编码以来,一直跟随着我的应用程序类型,那就是控制台应用程序。多年以来,它们看起来一直都是Main(string [] args)和一些幼稚的不一致的命令行解析器。随着各种OSS帮助程序库的采用,这种情况逐渐得到改善。在本文中,我将逐步介绍今天是dotnet新控制台的替代起点,该方法可以大大减少日志记录,解析和参数验证所需的样板代码,让我专注于解决的问题而不是管道。

搭建新项目的便捷方法是使用.NET SDK CLI的模板功能,它预装了一些模板,例如console,classlib等,但是除此之外,还可以创建自己的模板,为方便起见,我为您提供了便利,因此,安装了.NET 5 SDK后,您便可以轻松尝试看一下本文中讨论的所有内容。

因此,根据我认为正确的配方,我们开始创建一个新的控制台应用程序。.NETSDK模板以NuGet包的形式分发,NuGet包的规范来源是NuGet.org,我已在其中发布了我的模板作为Devlead.Console.Template。使用dotnet new命令和--install packageId参数安装模板,在这种情况下:

在本地安装模板之后,我们现在可以使用新的devleadconsole模板,根据我,基本依赖项和样板代码创建新的控制台应用程序:

MyConsoleApp│MyConsoleApp.csproj│Program.cs│└───命令│ConsoleCommand.cs│├───Settings│ConsoleSettings.cs│└───验证ValidateStringAttribute.cs

< Project Sdk =" Microsoft.NET.Sdk"> < PropertyGroup> < OutputType> Exe< / OutputType> < TargetFramework> net5.0< / TargetFramework> < Nullable>启用< / Nullable> < TreatWarningsAsErrors> true< / TreatWarningsAsErrors> < / PropertyGroup> < ItemGroup> < PackageReference Include =" Spectre.Console"版本=" 0.37.0" /> < PackageReference Include =" Spectre.Cli.Extensions.DependencyInjection"版本=" 0.3.0" /> < PackageReference Include =" Microsoft.Extensions.Logging"版本=" 5.0.0" /> < PackageReference Include =" Microsoft.Extensions.Logging.Console"版本=" 5.0.0" /> < PackageReference Include =" Microsoft.SourceLink.GitHub"版本=" 1.0.0" PrivateAssets ="全部" /> < / ItemGroup>< / Project>

Nullable与值enable一起启用,启用C#8中引入的可为空的引用类型功能,默认情况下使引用类型为不可为空,基本上将许多错误从运行时较晚捕获到编译时早期捕获。

值为true的TreatWarningsAsErrors使编译器变得更加笨拙,它不仅会破坏编译器错误的构建,还会破坏编译器的警告,并与Nullable结合使用,我个人认为代码质量从一开始就会变得更好。

Spectre.Console有很多东西,对于从事控制台应用程序的任何人来说都是一支真正的瑞士军队,但是在此模板中,它最重要的是一个非常固执的命令行解析器。

Spectre.Cli.Extensions.DependencyInjection使Spectre.Console易于与标准Microsoft DependencyInjection互操作,默认情况下与用于ASP.NET和Azure函数的依赖项注入相同。

简化的Microsoft.Extensions.Logging提供了标准的日志记录抽象,Microsoft.Extensions.Logging.Console提供了用于记录到控制台的实现。

Microsoft.SourceLink.GitHub可以在工件和源代码控制之间进行自动跟踪,从而提供更好的调试和可跟踪性体验。

生成的Program.cs使用新的C#9顶级语句模式从应用程序中删除了不必要的仪式代码,但它包含的内容:

注意:顶层语句表示随着RunAsync返回Task< int>,。NET 5会自动生成"程序"类和异步Task< int> Main(string args)为您服务,无需编写大量样板代码。

使用Microsoft.Extensions.DependencyInjection;使用Microsoft.Extensions.Logging;使用Devlead.Console.Commands;使用Spectre.Console.Cli;使用Spectre.Cli.Extensions.DependencyInjection; var serviceCollection = new ServiceCollection().AddLogging(configure => ;配置.AddSimpleConsole(opts => {opts.TimestampFormat =" yyyy-MM-dd HH:mm:ss&#34 ;;}))));使用var registrar = new DependencyInjectionRegistrar(serviceCollection); var app =新的CommandApp(registrar); app.Configure(config => {config.ValidateExamples(); config.AddCommand< ConsoleCommand>(&" console").WithDescription("示例控制台命令。&# 34;).WithExample(new [] {" console"});});返回等待的app.RunAsync(args);

ConsoleCommand.cs包含" just"您的业​​务代码Spectre.Console处理了繁重的解析和验证命令行参数(基于提供的设置类,在后面的更多内容中。),使用依赖注入来解析构造函数参数,等等。域而不是样板代码,从而获得与Azure Function或.NET Workers非常相似的体验,从而可以重用模式和代码。 Spectre.Console支持异步和同步命令。

使用System.Threading.Tasks;使用Microsoft.Extensions.Logging;使用MyConsoleApp.Commands.Setting;使用Spectre.Console.Cli;命名空间MyConsoleApp.Commands {公共类ConsoleCommand:AsyncCommand< ConsoleSettings> {私人ILogger记录器{get; } public over async Task< int> ExecuteAsync(CommandContext上下文,ConsoleSettings设置){Logger.LogInformation("必填:{Mandatory}",settings.Mandatory); Logger.LogInformation("可选:{Optional}&#34 ;, settings.Optional); Logger.LogInformation(" CommandOptionFlag:{CommandOptionFlag}&#34 ;, settings.CommandOptionFlag); Logger.LogInformation(" CommandOptionValue:{CommandOptionValue}",settings.CommandOptionValue);返回等待Task.FromResult(0); } public ConsoleCommand(ILogger< ConsoleCommand> logger){Logger = logger; }}}

ConsoleSettings.cs包含每个命令具有哪些参数(如果它们是必需的/可选的),位置以及如何验证的定义。它还包含用于自动生成帮助和错误消息的元数据。

使用System.ComponentModel;使用Devlead.Console.Commands.Validate;使用Spectre.Console.Cli;命名空间Devlead.Console.Commands.Setting {公共类ConsoleSettings:CommandSettings {[CommandArgument(0,"< mandatory>& #34;)] [Description(" Mandatory arguments")] public string Mandatory {get;组; } = string.Empty; [CommandArgument(1," [optional]")] [Description(" Optional arguments")]公共字符串?可选{get;组; } [CommandOption("-command-option-flag")] [Description(" Command option flag。")] public bool CommandOptionFlag {get;组; } [CommandOption("-command-option-value< value>")] [Description(" Command option value。")] [ValidateString]公共字符串? CommandOptionValue {get;组; }}}

Spectre.Console可以通过属性的自定义属性(请参见ConsoleSettings.CommandOptionValue的示例)进行验证,也可以通过覆盖CommandSettings的Validate()方法进行全局验证。该模板附带一个示例ValidateStringAttribute,该示例仅验证字符串的长度,但是您可以根据需要将其设置为高级。

使用Spectre.Console;使用Spectre.Console.Cli;命名空间MyConsoleApp.Commands.Validation {public class ValidateStringAttribute:ParameterValidationAttribute {public const int MinimumLength = 3; public ValidateStringAttribute():base(errorMessage:null){}公共重写ValidationResult Validate(ICommandParameterInfo parameterInfo,object?value)=> (作为字符串的值)开关{{长度:> = MinimumLength} => ValidationResult.Success(),{长度:< MinimumLength} => ValidationResult.Error($" {parameterInfo?.PropertyName}({value})的长度至少应为{MinimumLength}个字符。"),_ => ValidationResult.Error($"指定了无效的{parameterInfo?.PropertyName}({value})。")}; }}

这是我认为自己做.NET控制台应用程序的快乐之路,请随时告诉我您是否有成功的秘诀,但必须说,我真的很高兴这种组合如何使我编写控制台应用程序 以与我处理.NET工作人员,Azure Functions,ASP .NET Core等相同的方式,确保模式和代码的一致性,较少的重复和良好的重用。 与命令行解析相比,Spectre.Console有很多功能,我强烈建议您检查一下它必须提供的所有其他功能。