使用.Net Core编写命令行工具(CLI)

命令行工具(CLI)

  命令行工具(CLI)是在图形用户界面得到普及之前使用最为广泛的用户界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行。

  通常认为,命令行工具(CLI)没有图形用户界面(GUI)那么方便用户操作。因为,命令行工具的软件通常需要用户记忆操作的命令,但是,由于其本身的特点,命令行工具要较图形用户界面节约计算机系统的资源。在熟记命令的前提下,使用命令行工具往往要较使用图形用户界面的操作速度要快。所以,图形用户界面的操作系统中,都保留着可选的命令行工具。

  另外,命令行工具(CLI)应该是一个开箱即用的工具,不需要安装任何依赖。

  一些熟悉的CLI工具如下:

  1. dotnet cli

  2. vue cli

  3. angular cli

  4. aws cli

  5. azure cli

指令设计

  本文将使用.Net Core(版本3.1.102)编写一个CLI工具,实现配置管理以及条目(item)管理(调用WebApi实现),详情如下:

  

框架说明

  编写CLI使用的主要框架是CommandLineUtils,它主要有以下优势:

  1. 良好的语法设计

  2. 支持依赖注入

  3. 支持generic host

WebApi

  提供api让cli调用,实现条目(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 - 函数入口

[HelpOption(Inherited = true)] //显示指令帮助,并且让子指令也继承此设置
[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 - 实现的功能比较简单,主要是指令描述以及指定对应的子指令

[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 - 配置管理的具体实现,主要是文件读写

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{//弹出交互框,让用户输入,设置默认值为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 - 调用Web Api的具体实现,使用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));}
}

如何发布

  在项目文件中设置发布程序的名称(AssemblyName):

 <PropertyGroup><OutputType>Exe</OutputType><TargetFramework>netcoreapp3.1</TargetFramework><AssemblyName>api-cli</AssemblyName></PropertyGroup>

  进入控制台程序目录:

cd src/NetCoreCLI

  发布Linux使用版本:

dotnet publish -c Release -r linux-x64 /p:PublishSingleFile=true

  发布Windows使用版本:

dotnet publish -c Release -r win-x64 /p:PublishSingleFile=true

  发布MAC使用版本:

 dotnet publish -c Release -r osx-x64 /p:PublishSingleFile=true

使用示例

  这里使用Linux作为示例环境。

  1. 以docker的方式启动web api

  

  2. 虚拟机上没有安装.net core的环境

  

  3. 把编译好的CLI工具拷贝到虚拟机上,授权并移动到PATH中(如果不移动,可以通过./api-cli的方式调用)

  sudo chmod +x api-cli #授权sudo mv ./api-cli /usr/local/bin/api-cli #移动到PATH

  4. 设置配置文件:api-cli config set

  

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

  

  6. 创建条目: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. 错误指令:api-cli xxx

  


源码地址

  https://github.com/ErikXu/NetCoreCLI


参考资料

https://docs.microsoft.com/en-us/dotnet/core/rid-catalog#using-rids](https://docs.microsoft.com/en-us/dotnet/core/rid-catalog#using-rids

https://medium.com/swlh/build-a-command-line-interface-cli-program-with-net-core-428c4c85221

关注架构师高级俱乐部

开启架构之路

使用.Net Core编写命令行工具(CLI)相关推荐

  1. java 编写命令行工具_编写命令行工具

    1.使用common-cli编写命令行工具 commons-cli是Apache开源组织提供的用于解析命令行参数的包. 先引用common-cli依赖包: commons-cli commons-cl ...

  2. python工具是什么-使用Python编写命令行工具有什么好的库?

    使用Python编写命令行工具的库很多,我最推荐的还是Google Fire Hello World 要介绍Fire是什么,看一个简单的例子就明白了 # calc.py import fire cla ...

  3. go编写命令行工具_编写者的命令行文档转换工具

    go编写命令行工具 今天,我们有足够的工具可用于在我们的计算机上编辑备忘录,信件,论文,书籍,演示幻灯片和其他文档. 这既有好处也有缺点:一方面,如果您不喜欢某个软件,则可以随时随地转到另一个软件上: ...

  4. swift编写命令行工具

    2019独角兽企业重金招聘Python工程师标准>>> 原文: https://www.raywenderlich.com/128039/command-line-programs- ...

  5. node工程默认url_node命令行工具之实现项目工程自动初始化的标准流程

    一.目的 传统的前端项目初始流程一般是这样: 可以看出,传统的初始化步骤,花费的时间并不少.而且,人工操作的情况下,总有改漏的情况出现.这个缺点有时很致命. 甚至有马大哈,没有更新项目仓库地址,导致提 ...

  6. python3命令需要使用命令行开发者工具_3 个 Python 命令行工具

    用 Click.Docopt 和 Fire 库写你自己的命令行应用. 有时对于某项工作来说一个命令行工具就足以胜任.命令行工具是一种从你的 shell 或者终端之类的地方交互或运行的程序.Git 和 ...

  7. 中文 Markdown 编写格式规范的命令行工具 lint-md

    lint-md 用于检查中文 markdown 编写格式规范的命令行工具,基于 AST 开发,且方便集成 ci.Cli tool to lint your markdown file for Chin ...

  8. Jenkins CLI命令行工具,助你轻松管理 Jenkins

    Jenkins CLI,简称 jcli,一个使用 Golang 开发的开源的 Jenkins 命令行工具.它可以帮忙你轻松地管理 Jenkins.无论你是 Jenkins 插件开发者,还是 Jenki ...

  9. 使用 Apache Commons CLI 开发命令行工具

    http://www.ibm.com/developerworks/cn/java/j-lo-commonscli/index.html 使用 Apache Commons CLI 开发命令行工具 杨 ...

最新文章

  1. eclipse占用内存过大_MySQL 服务占用cpu 100%,如何排查问题? (MySQL面试第七弹)...
  2. 通过JAVA对HDFS进行操作管理插件
  3. ubuntu配置GDB
  4. 青苹果一键重装系统安装VS2015
  5. 【报告分享】线上汉服消费洞察报告.pdf(附下载链接)
  6. 微信公众号开发系列教程一(调试环境部署续:vs远程调试)
  7. 17年北邮计算机应用基础,2017计算机应用基础考试题及答案
  8. memmove、memcpy和memccpy简介
  9. 软件开发模型_为什么越来越多软件开发团队都放弃了瀑布模型?
  10. SQL语法中的JOIN类型
  11. oracle 的insert into的详解
  12. 拒绝焦虑状态:TA爱我吗?
  13. python orm框架
  14. mybatis 绑定失败:Invalid bound statement (not found): com.demo.service.api.dao.SysUserMapper.insert
  15. webpack 的安装与使用
  16. JavaScript学习笔记|数据类型——Object类型、for in循环
  17. [HNOI2005]狡猾的商人 差分约束+判环
  18. (NCRE网络技术)中小型网络系统总体规划与设计方法-知识点
  19. VSCode 开启||关闭右侧预览功能
  20. botpress搭建智能问答机器人

热门文章

  1. 《C prime plus (第五版)》 ---第11章 字符串和字符串函数---4
  2. ubuntu 14.04 安装Java JDK
  3. hdu 2648 Shopping
  4. 如何在PowerPoint中自动调整图片大小
  5. dropbox_Google的新存储定价与Microsoft,Apple和Dropbox相比如何
  6. nodejs和Vue和Idea
  7. jQuery初识和常用事件(一)
  8. chrome插件网站
  9. WebApi的调用-3.Basic验证
  10. 多云战略:企业如何精益求精?