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

使用.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

关注架构师高级俱乐部

开启架构之路

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/311080.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

与php有区别_php://output和php://stdout的区别

php中文网最新课程每日17点准时技术干货分享PHP包含了以php://开头的一系列输出输出流&#xff0c;如php://stdin, php://stdout等。今天查看代码时&#xff0c;忽然想到一个问题&#xff1a;php://output和php://stdout有什么区别&#xff1f;从PHP的官方文献中找答案&#xf…

浅谈 Kubernetes Scheduling-Framework 插件的实现

最近几个月一直在研究 kubernetes 的 scheduling-framework 调度框架&#xff0c;发现还是十分有意思的&#xff0c;我自己也实现了一个基于 scheduling-framework 调度框架的自定义调度器&#xff0c;希望感兴趣的同学一起学习&#xff1a;https://github.com/NJUPT-ISL/Yoda-…

C++实现线段树(lazy-tag方法)-区间修改,区间查询

代码如下&#xff1a; #include <iostream> using namespace std; const int N 10010; typedef long long LL; LL input[N];struct node {int l, r;LL sum;LL add; } tree[4 * N];void build(int l, int r, int u) {tree[u].l l;tree[u].r r;if (l r) {tree[u].sum …

.NET Core开发实战(第25课:路由与终结点:如何规划好你的Web API)--学习笔记(上)...

25 | 路由与终结点&#xff1a;如何规划好你的Web API路由系统在 ASP.NET MVC 框架里面就已经存在了&#xff0c;在 ASP.NET Core 框架里面进行了改进路由系统的核心作用是指 URL 和 应用程序 Controller 的对应关系的一种映射这个映射关系实际上有两种作用&#xff1a;1、把 U…

Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权(一)

好吧&#xff0c;这个题目我也想了很久&#xff0c;不知道如何用最简单的几个字来概括这篇文章&#xff0c;原本打算取名《Angular单页面应用基于Ocelot API网关与IdentityServer4ASP.NET Identity实现身份认证与授权》&#xff0c;然而如你所见&#xff0c;这样的名字实在是太…

【翻译】.NET 5 Preview 1 发布

.NET 5 Preview 1 发布去年年底,我们发布了.NET Core 3.0和3.1.这些版本添加了桌面应用程序模型Windows Forms(WinForms)和WPF,ASP.NET Blazor用于构建SPA应用程序和用于构建分布式应用和服务的gRPC模板、用于与gRPC对话丰富的客户端代码生成、REST API服务等等.我们很高兴看到…

Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权(二)

上文已经介绍了Identity Service的实现过程。今天我们继续&#xff0c;实现一个简单的Weather API和一个基于Ocelot的API网关。回顾《Angular SPA基于Ocelot API网关与IdentityServer4的身份认证与授权&#xff08;一&#xff09;》Weather APIWeather API实现非常简单&#xf…

编程 音量键_盘点市面上那些千元级高逼格的键盘 灯光炫酷 多宏编程

随着科技的变化&#xff0c;以及电竞被更多人熟知&#xff0c;也带动了电竞外设的进步&#xff0c;现在更多的人喜欢选择机械键盘。首先机械键盘可以给我们带来超棒的敲击感&#xff0c;无论是玩游戏还是日常办公打字&#xff0c;都绝对是一等一的好。再者机械键盘在高强度使用…

DotNetCore Web应用程序中的Cookie管理

原文来自互联网&#xff0c;由长沙DotNET技术社区编译。如译文侵犯您的署名权或版权&#xff0c;请联系小编&#xff0c;小编将在24小时内删除。限于译者的能力有限&#xff0c;个别语句翻译略显生硬&#xff0c;还请见谅。作者简介&#xff1a;Jon&#xff08;Jonathan&#x…

逆向so_记一次APP的so层算法逆向(七)

“ 前言&#xff1a;初学逆向 请多多指教 好累 感觉每天这样肝 人有点受不了了...”学习到的内容—1、新学习到IDA的一些分析时候的小技巧2、算法还原代码实现的练习(有个参数没有分析出来&#xff0c;后面知道了会补上的)3、在Frida中使用命令行调试的方便方法分析过程—APP登…

C++实现Huffman树

代码如下&#xff1a; #include <iostream> using namespace std; int s1, s2;typedef struct {int weight;int parent, lch, rch; } HTNode, *HuffmanTree;void Select(HuffmanTree &HT, int n, int &s1, int &s2) {int minv;//定义一个临时变量存储最小值…

.NET Core开发实战(第25课:路由与终结点:如何规划好你的Web API)--学习笔记(下)...

25 | 路由与终结点&#xff1a;如何规划好你的Web API自定义约束实现了路由约束接口&#xff0c;它只有一个 Match 方法&#xff0c;这个方法传入了 Http 当前的 httpContext&#xff0c;route&#xff0c;routeKey这个 routeKey 就是我们要验证的 key 值后面两个参数 RouteVal…

微软 Visual Studio 2019 16.5 发布:.NET 移动开发、生产力

微软最新发布了 Visual Studio 2019 16.5 版本&#xff0c;下面来看看主要更新内容&#xff1a;.NET 移动开发首先要讨论的特性是 XAML Hot Reload for Xamarin.Forms。此功能可加快开发速度&#xff0c;并使开发者可以更轻松地在移动应用的用户界面上进行构建、实验和迭代。且…

chrome主题_谷歌Chrome将很快允许用户创建自定义主题

站长之家(ChinaZ.com) 7月31日 消息:据9to5google报道&#xff0c;虽然用户可以通过Chrome Web Store定制主题&#xff0c;但用户要根据自己的独特喜好定制主题却不是一个简单的事。谷歌正寻求通过在Chrome内置一个自定义主题生成器来解决这个问题。Chrome Web Store中有许多传…

使用Magicodes.IE.Excel完成Excel图片的导入和导出

说明本章教程主要说明如何使用Magicodes.IE.Excel进行图片的导入导出。要点配置DTO进行Excel图片导出配置DTO进行Excel图片导入图片导入导出特性说明ExportImageFieldAttributeHeight&#xff1a;高度(默认15)Width&#xff1a;宽度(默认50)Alt&#xff1a;图片不存在时替换文本…

C++未定义行为-数组越界

我们先来看看下面的代码&#xff1a; #include <iostream> using namespace std; const int N 100010; int a[N]; int main() {for (int i 1;i<N;i) a[i] 2;return 0; }当我们写这段代码的时候&#xff0c;编译器就会发生这样的问题。 这是为什么呢&#xff1f;&a…

SuperBenchmarker一个用.NET编写的压测工具

0x01 前言在这之前想必大家对ab(http)与abs(https)也有一些了解,我们今天不去看ab和abs,SuperBenchmarker(sb.exe)是一个压测工具,他是一个受Apache Benchmark的启发,他会在终端窗口为我们显示最终的结果,同时也会在web界面生成一个动态结果。SuperBenchmarker(sb.exe)可以在Wi…

mysql文献综述_文献综述随笔(二十)

一、基本信息标题&#xff1a;中小型酒店管理系统的设计与实现时间&#xff1a;2013来源&#xff1a;厦门大学关键词&#xff1a;MVC;B/S;JAVA EE;JSP;MySQL;瀑布开发模型二、研究内容1.主要内容&#xff1a;系统业务需求、功能需求、系统架构设计、数据库设计1.1功能模块设计&…

五分钟完成 ABP vNext 通讯录 App 开发

ABP vNext&#xff08;后文简称Abp&#xff09;是 Volo 公司堪称艺术品级的应用开发框架&#xff0c;它基于领域驱动设计&#xff08;DDD&#xff09;的思维&#xff0c;创新地采用了模块化的设计。Abp 目前无疑是 ASP.NET Core 开发框架中最先进和最优雅的存在。笔者认为&…

mysql 5.74安装教程_MySQL安装、基本账户安全(5.0以后版本)

-----------MySQL 5.0以后版本的安装-----------MySQL安装安装包学习的必杀绝技——就是阅读包的安装说明(readme & install)文档。----------# rm /etc/my.cnf (安装前执行一下)----------1.Mysql-5.0.40.tar.gz1.1.Source Installation Overview(lines 74 of …