Add a CLI to an ASP.NET Core web application that supports dependency injection, authorization and authentication with as little as two lines of code.
You should install WebCommandLine with NuGet:
Install-Package WebCommandLine
Or via the .NET command line interface (.NET CLI):
dotnet add package WebCommandLine
Either commands, from Package Manager Console or .NET Core CLI, will allow download and installation of WebCommandLine and all its required dependencies.
First, configure WebCommandLine to know where the commands are located, in the startup of your application:
var builder = WebApplication.CreateBuilder(args);
//...
builder.Services.AddWebCommandLine(typeof(MyClass));// Tells WebCommandLine which assembly to scan for console commands
//...
app.UseWebCommandLine();
Create a class that implements the IConsoleCommand interface and add the ConsoleCommandAttribute to the class definition:
[ConsoleCommand("echo", "Echos back the first arg received")]
public class Echo : IConsoleCommand
{
public Task<ConsoleResult> RunAsync(string[] args)
{
if (args.Length != 0)
{
return Task.FromResult(new ConsoleResult(args[0]));
}
return Task.FromResult(ConsoleResult.CreateError("I didn't hear anything!"));
}
}
Run the application and then press the CTRL + ` keys to launch the web command line.
You can also implement the abstract base class ConsoleCommandBase to support help text for your commands by passing '?' or help as argument when running your command:
[ConsoleCommand("greet", "Returns a greeting message")]
public class Greet : ConsoleCommandBase
{
public override ConsoleResult Help()
{
var sb = new StringBuilder("<table class='webcli-tbl'><tr><td colspan='3' class='webcli-val'>Lists available arguments</td></tr>");
sb.Append("<tr><td class='webcli-lbl'>USAGE:</td><td colspan='2' class='webcli-val'>greet Nyron</td></tr>");
sb.Append("</table>");
return new ConsoleResult(sb.ToString()) { isHTML = true };
}
protected override Task<ConsoleResult> RunAsyncCore(string[] args)
{
if (args.Length == 0)
{
return Task.FromResult(ConsoleResult.CreateError("Invalid argument pass"));
}
return Task.FromResult(new ConsoleResult($"Hello, {args[0]}. Nice to meet you!!") { isHTML = false });
}
}
There is also support for strongly typed commands. The example below shows how to implement a strongly typed command using Fluent Command Line Parser to parse the arguments:
public class AddUserArguments
{
public string UserName { get; set; }
public string Password { get; set; }
public List<string> Claims { get; set; }
}
[ConsoleCommand("add-user", "adds a new user account")]
public class AddUser : ConsoleCommandBase<AddUserArguments>
{
protected readonly FluentCommandLineParser<AddUserArguments> _parser;
public AddUser()
{
_parser = new FluentCommandLineParser<AddUserArguments>();
// specify which property the value will be assigned too.
_parser.Setup(arg => arg.UserName)
.As('n', "userName") // define the short and long option name
.Required(); // using the standard fluent Api to declare this Option as required.
_parser.Setup(arg => arg.Password)
.As('p', "password")
.SetDefault("P@$$w0rd");
_parser.Setup(arg => arg.Claims)
.As('c', "claims");
}
public override ConsoleResult Help()
{
//add-user -n userName -p password -c claims [optional]
var sb = new StringBuilder("<table class='webcli-tbl'><tr><td colspan='3' class='webcli-val'>Lists available arguments</td></tr>");
sb.Append("<tr><td class='webcli-lbl'>-n</td><td>:</td><td class='webcli-val'>Name that uniquely identifies user</td></tr>");
sb.Append("<tr><td class='webcli-lbl'>-p</td><td>:</td><td class='webcli-val'>User password, Default will be used is not value is provided</td></tr>");
sb.Append("<tr><td class='webcli-lbl'>-c</td><td>:</td><td class='webcli-val'>Claims that determine what functions the user can access. Valid options includes: reports,user, customer & webcli (optional)</td></tr>");
sb.Append("<tr><td class='webcli-lbl'>USAGE:</td><td colspan='2' class='webcli-val'>add-user -n nyron.williams@willcorp.com -p MySecretPassword -c \"report,user,webcli\" -w 1</td></tr>");
sb.Append("</table>");
return new ConsoleResult(sb.ToString()) { isHTML = true };
}
protected override CommandLineParserResult<AddUserArguments> Parse(string[] args)
{
var result = _parser.Parse(args);
return new CommandLineParserResult<AddUserArguments>(_parser.Object, result.ErrorText);
}
protected override Task<ConsoleResult> RunAsyncCore(AddUserArguments userToAdd)
{
if (!userToAdd.UserName.Equals("foo", StringComparison.OrdinalIgnoreCase))
{
return Task.FromResult(new ConsoleResult($"User created successfully"));
}
else
{
return Task.FromResult(ConsoleResult.CreateError("Invalid username"));
}
}
}
WebCommandLine leverages existing ASP.NET Authorization features and requires little effort for integration. The WebCommandLine endpoint can be secured by setting the Authorization property of the WebCommandLineConfiguration class when calling the AddWebCommandLine method during your application startup.
//...
builder.Services.AddWebCommandLine(options =>
{
options.Authorization = new[] { new WebCommandLineAuthorization { Policy = "AdminUser", Roles="Operator,Developer" } };
});
//...
//Adding sample policies
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminUser", policyBuilder =>
{
policyBuilder.RequireAuthenticatedUser();
policyBuilder.RequireClaim("isAdmin", "true");
});
options.AddPolicy("PowerUser", policyBuilder =>
{
policyBuilder.RequireAuthenticatedUser();
policyBuilder.AddRequirements(new WebCmdLineRequirement());
policyBuilder.RequireAssertion(ctx =>
{
return ctx.User.IsInRole("BusinessAdmin");
});
});
});
You can change WebCommandLine url base paths by modifying the WebCommandLineConfiguration class during your application startup.
//...
builder.Services.AddWebCommandLine(options =>
{
options.StaticFilesUrl = "/MyWebAssets"; //This will be the base path for static files
options.WebCliUrl = "/MyWebCli"; //command requests will go to this endpoint
});
//...