使用.Net Core編寫(xiě)命令行工具(CLI)的方法
命令行工具(CLI)
命令行工具(CLI)是在圖形用戶界面得到普及之前使用最為廣泛的用戶界面,它通常不支持鼠標(biāo),用戶通過(guò)鍵盤(pán)輸入指令,計(jì)算機(jī)接收到指令后,予以執(zhí)行。
通常認(rèn)為,命令行工具(CLI)沒(méi)有圖形用戶界面(GUI)那么方便用戶操作。因?yàn)?,命令行工具的軟件通常需要用戶記憶操作的命令,但是,由于其本身的特點(diǎn),命令行工具要較圖形用戶界面節(jié)約計(jì)算機(jī)系統(tǒng)的資源。在熟記命令的前提下,使用命令行工具往往要較使用圖形用戶界面的操作速度要快。所以,圖形用戶界面的操作系統(tǒng)中,都保留著可選的命令行工具。
另外,命令行工具(CLI)應(yīng)該是一個(gè)開(kāi)箱即用的工具,不需要安裝任何依賴。
一些熟悉的CLI工具如下:
1. dotnet cli
2. vue cli
4. aws cli
指令設(shè)計(jì)
本文將使用.Net Core(版本3.1.102)編寫(xiě)一個(gè)CLI工具,實(shí)現(xiàn)配置管理以及條目(item)管理(調(diào)用WebApi實(shí)現(xiàn)),詳情如下:

框架說(shuō)明
編寫(xiě)CLI使用的主要框架是CommandLineUtils,它主要有以下優(yōu)勢(shì):
1. 良好的語(yǔ)法設(shè)計(jì)
2. 支持依賴注入
3. 支持generic host
WebApi
提供api讓cli調(diào)用,實(shí)現(xiàn)條目(item)的增刪改查:
[Route("api/items")]
[ApiController]
public class ItemsController : ControllerBase
{
private readonly IMemoryCache _cache;
private readonly string _key = "items";
public ItemsController(IMemoryCache memoryCache)
{
_cache = memoryCache;
}
[HttpGet]
public IActionResult List()
{
var items = _cache.Get<List<Item>>(_key);
return Ok(items);
}
[HttpGet("{id}")]
public IActionResult Get(string id)
{
var item = _cache.Get<List<Item>>(_key).FirstOrDefault(n => n.Id == id);
return Ok(item);
}
[HttpPost]
public IActionResult Create(ItemForm form)
{
var items = _cache.Get<List<Item>>(_key) ?? new List<Item>();
var item = new Item
{
Id = Guid.NewGuid().ToString("N"),
Name = form.Name,
Age = form.Age
};
items.Add(item);
_cache.Set(_key, items);
return Ok(item);
}
[HttpDelete("{id}")]
public IActionResult Delete(string id)
{
var items = _cache.Get<List<Item>>(_key);
var item = items?.SingleOrDefault(n => n.Id == id);
if (item == null)
{
return NotFound();
}
items.Remove(item);
_cache.Set(_key, items);
return Ok();
}
}
CLI
1. Program - 函數(shù)入口
[HelpOption(Inherited = true)] //顯示指令幫助,并且讓子指令也繼承此設(shè)置
[Command(Description = "A tool to communicate with web api"), //指令描述
Subcommand(typeof(ConfigCommand), typeof(ItemCommand))] //子指令
class Program
{
public static int Main(string[] args)
{
//配置依賴注入
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(PhysicalConsole.Singleton);
serviceCollection.AddSingleton<IConfigService, ConfigService>();
serviceCollection.AddHttpClient<IItemClient, ItemClient>();
var services = serviceCollection.BuildServiceProvider();
var app = new CommandLineApplication<Program>();
app.Conventions
.UseDefaultConventions()
.UseConstructorInjection(services);
var console = (IConsole)services.GetService(typeof(IConsole));
try
{
return app.Execute(args);
}
catch (UnrecognizedCommandParsingException ex) //處理未定義指令
{
console.WriteLine(ex.Message);
return -1;
}
}
//指令邏輯
private int OnExecute(CommandLineApplication app, IConsole console)
{
console.WriteLine("Please specify a command.");
app.ShowHelp();
return 1;
}
}
2. ConfigCommand和ItemCommand - 實(shí)現(xiàn)的功能比較簡(jiǎn)單,主要是指令描述以及指定對(duì)應(yīng)的子指令
[Command("config", Description = "Manage config"),
Subcommand(typeof(GetCommand), typeof(SetCommand))]
public class ConfigCommand
{
private int OnExecute(CommandLineApplication app, IConsole console)
{
console.Error.WriteLine("Please submit a sub command.");
app.ShowHelp();
return 1;
}
}
[Command("item", Description = "Manage item"),
Subcommand(typeof(CreateCommand), typeof(GetCommand), typeof(ListCommand), typeof(DeleteCommand))]
public class ItemCommand
{
private int OnExecute(CommandLineApplication app, IConsole console)
{
console.Error.WriteLine("Please submit a sub command.");
app.ShowHelp();
return 1;
}
}
3.ConfigService - 配置管理的具體實(shí)現(xiàn),主要是文件讀寫(xiě)
public interface IConfigService
{
void Set();
Config Get();
}
public class ConfigService: IConfigService
{
private readonly IConsole _console;
private readonly string _directoryName;
private readonly string _fileName;
public ConfigService(IConsole console)
{
_console = console;
_directoryName = ".api-cli";
_fileName = "config.json";
}
public void Set()
{
var directory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), _directoryName);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
var config = new Config
{
//彈出交互框,讓用戶輸入,設(shè)置默認(rèn)值為http://localhost:5000/
Endpoint = Prompt.GetString("Specify the endpoint:", "http://localhost:5000/")
};
if (!config.Endpoint.EndsWith("/"))
{
config.Endpoint += "/";
}
var filePath = Path.Combine(directory, _fileName);
using (var outputFile = new StreamWriter(filePath, false, Encoding.UTF8))
{
outputFile.WriteLine(JsonConvert.SerializeObject(config, Formatting.Indented));
}
_console.WriteLine($"Config saved in {filePath}.");
}
public Config Get()
{
var filePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), _directoryName, _fileName);
if (File.Exists(filePath))
{
var content = File.ReadAllText(filePath);
try
{
var config = JsonConvert.DeserializeObject<Config>(content);
return config;
}
catch
{
_console.WriteLine("The config is invalid, please use 'config set' command to reset one.");
}
}
else
{
_console.WriteLine("Config is not existed, please use 'config set' command to set one.");
}
return null;
}
}
4.ItemClient - 調(diào)用Web Api的具體實(shí)現(xiàn),使用HttpClientFactory的方式
public interface IItemClient
{
Task<string> Create(ItemForm form);
Task<string> Get(string id);
Task<string> List();
Task<string> Delete(string id);
}
public class ItemClient : IItemClient
{
public HttpClient Client { get; }
public ItemClient(HttpClient client, IConfigService configService)
{
var config = configService.Get();
if (config == null)
{
return;
}
client.BaseAddress = new Uri(config.Endpoint);
Client = client;
}
public async Task<string> Create(ItemForm form)
{
var content = new StringContent(JsonConvert.SerializeObject(form), Encoding.UTF8, "application/json");
var result = await Client.PostAsync("/api/items", content);
if (result.IsSuccessStatusCode)
{
var stream = await result.Content.ReadAsStreamAsync();
var item = Deserialize<Item>(stream);
return $"Item created, info:{item}";
}
return "Error occur, please again later.";
}
public async Task<string> Get(string id)
{
var result = await Client.GetAsync($"/api/items/{id}");
if (result.IsSuccessStatusCode)
{
var stream = await result.Content.ReadAsStreamAsync();
var item = Deserialize<Item>(stream);
var response = new StringBuilder();
response.AppendLine($"{"Id".PadRight(40, ' ')}{"Name".PadRight(20, ' ')}Age");
response.AppendLine($"{item.Id.PadRight(40, ' ')}{item.Name.PadRight(20, ' ')}{item.Age}");
return response.ToString();
}
return "Error occur, please again later.";
}
public async Task<string> List()
{
var result = await Client.GetAsync($"/api/items");
if (result.IsSuccessStatusCode)
{
var stream = await result.Content.ReadAsStreamAsync();
var items = Deserialize<List<Item>>(stream);
var response = new StringBuilder();
response.AppendLine($"{"Id".PadRight(40, ' ')}{"Name".PadRight(20, ' ')}Age");
if (items != null && items.Count > 0)
{
foreach (var item in items)
{
response.AppendLine($"{item.Id.PadRight(40, ' ')}{item.Name.PadRight(20, ' ')}{item.Age}");
}
}
return response.ToString();
}
return "Error occur, please again later.";
}
public async Task<string> Delete(string id)
{
var result = await Client.DeleteAsync($"/api/items/{id}");
if (result.IsSuccessStatusCode)
{
return $"Item {id} deleted.";
}
if (result.StatusCode == HttpStatusCode.NotFound)
{
return $"Item {id} not found.";
}
return "Error occur, please again later.";
}
private static T Deserialize<T>(Stream stream)
{
using var reader = new JsonTextReader(new StreamReader(stream));
var serializer = new JsonSerializer();
return (T)serializer.Deserialize(reader, typeof(T));
}
}
如何發(fā)布
在項(xiàng)目文件中設(shè)置發(fā)布程序的名稱(AssemblyName):
<PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp3.1</TargetFramework> <AssemblyName>api-cli</AssemblyName> </PropertyGroup>
進(jìn)入控制臺(tái)程序目錄:
cd src/NetCoreCLI
發(fā)布Linux使用版本:
dotnet publish -c Release -r linux-x64 /p:PublishSingleFile=true
發(fā)布Windows使用版本:
dotnet publish -c Release -r win-x64 /p:PublishSingleFile=true
發(fā)布MAC使用版本:
dotnet publish -c Release -r osx-x64 /p:PublishSingleFile=true
使用示例
這里使用Linux作為示例環(huán)境。
1. 以docker的方式啟動(dòng)web api

2. 虛擬機(jī)上沒(méi)有安裝.net core的環(huán)境

3. 把編譯好的CLI工具拷貝到虛擬機(jī)上,授權(quán)并移動(dòng)到PATH中(如果不移動(dòng),可以通過(guò)./api-cli的方式調(diào)用)
sudo chmod +x api-cli #授權(quán) sudo mv ./api-cli /usr/local/bin/api-cli #移動(dòng)到PATH
4. 設(shè)置配置文件:api-cli config set

5. 查看配置文件:api-cli config get

6. 創(chuàng)建條目:api-cli item create

7. 條目列表:api-cli item list

8. 獲取條目:api-cli item get

9. 刪除條目:api-cli item delete

10. 指令幫助:api-cli -h, api-cli config -h, api-cli item -h



11. 錯(cuò)誤指令:api-cli xxx

源碼地址
https://github.com/ErikXu/NetCoreCLI
參考資料
https://docs.microsoft.com/en-us/dotnet/core/rid-catalog
https://medium.com/swlh/build-a-command-line-interface-cli-program-with-net-core-428c4c85221
到此這篇關(guān)于使用.Net Core編寫(xiě)命令行工具(CLI)的方法的文章就介紹到這了,更多相關(guān).Net Core 命令行工具內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
.NET必知的EventCounters性能指標(biāo)監(jiān)視器詳解
這篇文章主要介紹了.NET必知的EventCounters性能指標(biāo)監(jiān)視器,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11
asp.net中的check與uncheck關(guān)鍵字用法解析
這篇文章主要介紹了asp.net中的check與uncheck關(guān)鍵字用法,以實(shí)例形式較為詳細(xì)的分析了check與uncheck關(guān)鍵字的各種常見(jiàn)用法與使用時(shí)的注意事項(xiàng),非常具有實(shí)用價(jià)值,需要的朋友可以參考下2014-10-10
asp.net(c#)Enterprise Library 3.0 下載
asp.net(c#)Enterprise Library 3.0 下載...2007-04-04
Javascript 直接調(diào)用服務(wù)器C#代碼 ASP.NET Ajax實(shí)例
近來(lái)總有一些朋友會(huì)問(wèn)到一些入門(mén)的問(wèn)題,把這些問(wèn)題整理一下,寫(xiě)出來(lái)。在以前的文章里,曾經(jīng)利用純JS編寫(xiě)過(guò)Ajax引擎,在真正開(kāi)發(fā)的時(shí)候,大家都不喜歡以這種低效率的方式開(kāi)發(fā),利用MS Ajax的集成的引擎,可以簡(jiǎn)單不少工作。2010-03-03
Asp.net 中mvc 實(shí)現(xiàn)超時(shí)彈窗后跳轉(zhuǎn)功能
這篇文章主要介紹了Asp.net 中mvc 實(shí)現(xiàn)超時(shí)彈窗后跳轉(zhuǎn)功能,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-02-02
MVC4 基礎(chǔ) 枚舉生成 DropDownList 實(shí)用技巧
本篇文章小編為大家介紹,MVC4 基礎(chǔ) 枚舉生成 DropDownList 實(shí)用技巧。需要的朋友參考下2013-04-04
.NET開(kāi)發(fā)基礎(chǔ):從簡(jiǎn)單的例子理解泛型 分享
.Net開(kāi)發(fā)基礎(chǔ)系列文章,對(duì)自己之前寫(xiě)過(guò)的代碼備忘,如能給人予幫助,不甚榮幸。個(gè)人能力有限,如有差錯(cuò)或不足,請(qǐng)及時(shí)指正。2013-06-06

