I worked on a .NET Core console application last week, and here are a few tips I want to pass along.
The McMaster.Extensions.CommandLineUtils package takes care of parsing command line arguments. You can describe the expected parameters using C# attributes, or using a builder API. I prefer the attribute approach, shown here:
[Option(Description = "Path to the conversion folder ", ShortName = "p")] [DirectoryExists] public string Path { get; protected set; } [Argument(0, Description ="convert | publish | clean")] [AllowedValues("convert", "publish", "clean")] public string Action { get; set; }
The basic concepts are Options
, Arguments
, and Commands
. The McMaster package, which was forked from an ASP.NET Core related repository, takes care of populating properties with values the user provides in the command line arguments, as well as displaying help text. You can read more about the behavior in the docs.
Running the app and asking for help provides some nicely formatted documentation.
If you are using dotnet
to execute an application, and the target application needs parameters, using a --
will delimit dotnet parameters from the application parameters. In other words, to pass a p
parameter to the application and not have dotnet
think you are passing a project path, use the following:
dotnet run myproject -- -p ./folder
The ServiceProvider we’ve learned to use in ASP.NET Core is also available in console applications. Here’s the code to configure services and launch an application that can accept the configured services in a constructor.
static void Main(string[] args) { var provider = ConfigureServices(); var app = new CommandLineApplication<Application>(); app.Conventions .UseDefaultConventions() .UseConstructorInjection(provider); app.Execute(args); } public static ServiceProvider ConfigureServices() { var services = new ServiceCollection(); services.AddLogging(c => c.AddConsole()); services.AddSingleton<IFileSystem, FileSystem>(); services.AddSingleton<IMarkdownToHtml, MarkdownToHtml>(); return services.BuildServiceProvider(); }
In this code, the class Application needs an OnExecute method. I like to separate the Program class (with the Main entry-point method) from the Application class that has Options, Arguments, and OnExecute.