热门标签 | HotTags
当前位置:  开发笔记 > 前端 > 正文

使用.NetCore编写命令行工具(CLI)的方法

这篇文章主要介绍了使用.NetCore编写命令行工具(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>(_key);
    return Ok(items);
  }

  [HttpGet("{id}")]
  public IActionResult Get(string id)
  {
    var item = _cache.Get>(_key).FirstOrDefault(n => n.Id == id);
    return Ok(item);
  }

  [HttpPost]
  public IActionResult Create(ItemForm form)
  {
    var items = _cache.Get>(_key) ?? new List();

    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>(_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();
    serviceCollection.AddHttpClient();

    var services = serviceCollection.BuildServiceProvider();

    var app = new CommandLineApplication();
    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(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 Create(ItemForm form);

  Task Get(string id);

  Task List();

  Task 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 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(stream);
      return $"Item created, info:{item}";
    }

    return "Error occur, please again later.";
  }

  public async Task Get(string id)
  {
    var result = await Client.GetAsync($"/api/items/{id}");

    if (result.IsSuccessStatusCode)
    {
      var stream = await result.Content.ReadAsStreamAsync();
      var item = Deserialize(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 List()
  {
    var result = await Client.GetAsync($"/api/items");

    if (result.IsSuccessStatusCode)
    {
      var stream = await result.Content.ReadAsStreamAsync();
      var items = Deserialize>(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 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(Stream stream)
  {
    using var reader = new JsonTextReader(new StreamReader(stream));
    var serializer = new JsonSerializer();
    return (T)serializer.Deserialize(reader, typeof(T));
  }
}

如何发布

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

 
   Exe
   netcoreapp3.1
   api-cli
  

  进入控制台程序目录:

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

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

到此这篇关于使用.Net Core编写命令行工具(CLI)的方法的文章就介绍到这了,更多相关.Net Core 命令行工具内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • Webmin远程命令执行漏洞复现及防护方法
    本文介绍了Webmin远程命令执行漏洞CVE-2019-15107的漏洞详情和复现方法,同时提供了防护方法。漏洞存在于Webmin的找回密码页面中,攻击者无需权限即可注入命令并执行任意系统命令。文章还提供了相关参考链接和搭建靶场的步骤。此外,还指出了参考链接中的数据包不准确的问题,并解释了漏洞触发的条件。最后,给出了防护方法以避免受到该漏洞的攻击。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • 如何使用PLEX播放组播、抓取信号源以及设置路由器
    本文介绍了如何使用PLEX播放组播、抓取信号源以及设置路由器。通过使用xTeve软件和M3U源,用户可以在PLEX上实现直播功能,并且可以自动匹配EPG信息和定时录制节目。同时,本文还提供了从华为itv盒子提取组播地址的方法以及如何在ASUS固件路由器上设置IPTV。在使用PLEX之前,建议先使用VLC测试是否可以正常播放UDPXY转发的iptv流。最后,本文还介绍了docker版xTeve的设置方法。 ... [详细]
  • 大坑|左上角_pycharm连接服务器同步写代码(图文详细过程)
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了pycharm连接服务器同步写代码(图文详细过程)相关的知识,希望对你有一定的参考价值。pycharm连接服务 ... [详细]
  • 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之六 || API项目整体搭建 6.1 仓储模式
    代码已上传Github+Gitee,文末有地址  书接上文:前几回文章中,我们花了三天的时间简单了解了下接口文档Swagger框架,已经完全解放了我们的以前的Word说明文档,并且可以在线进行调 ... [详细]
  • 现在比较流行使用静态网站生成器来搭建网站,博客产品着陆页微信转发页面等。但每次都需要对服务器进行配置,也是一个重复但繁琐的工作。使用DockerWeb,只需5分钟就能搭建一个基于D ... [详细]
  • Skywalking系列博客1安装单机版 Skywalking的快速安装方法
    本文介绍了如何快速安装单机版的Skywalking,包括下载、环境需求和端口检查等步骤。同时提供了百度盘下载地址和查询端口是否被占用的命令。 ... [详细]
  • Centos7.6安装Gitlab教程及注意事项
    本文介绍了在Centos7.6系统下安装Gitlab的详细教程,并提供了一些注意事项。教程包括查看系统版本、安装必要的软件包、配置防火墙等步骤。同时,还强调了使用阿里云服务器时的特殊配置需求,以及建议至少4GB的可用RAM来运行GitLab。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • Java验证码——kaptcha的使用配置及样式
    本文介绍了如何使用kaptcha库来实现Java验证码的配置和样式设置,包括pom.xml的依赖配置和web.xml中servlet的配置。 ... [详细]
  • imx6ull开发板驱动MT7601U无线网卡的方法和步骤详解
    本文详细介绍了在imx6ull开发板上驱动MT7601U无线网卡的方法和步骤。首先介绍了开发环境和硬件平台,然后说明了MT7601U驱动已经集成在linux内核的linux-4.x.x/drivers/net/wireless/mediatek/mt7601u文件中。接着介绍了移植mt7601u驱动的过程,包括编译内核和配置设备驱动。最后,列举了关键词和相关信息供读者参考。 ... [详细]
  • 使用正则表达式爬取36Kr网站首页新闻的操作步骤和代码示例
    本文介绍了使用正则表达式来爬取36Kr网站首页所有新闻的操作步骤和代码示例。通过访问网站、查找关键词、编写代码等步骤,可以获取到网站首页的新闻数据。代码示例使用Python编写,并使用正则表达式来提取所需的数据。详细的操作步骤和代码示例可以参考本文内容。 ... [详细]
  • 本文探讨了容器技术在安全方面面临的挑战,并提出了相应的解决方案。多租户保护、用户访问控制、中毒的镜像、验证和加密、容器守护以及容器监控都是容器技术中需要关注的安全问题。通过在虚拟机中运行容器、限制特权升级、使用受信任的镜像库、进行验证和加密、限制容器守护进程的访问以及监控容器栈,可以提高容器技术的安全性。未来,随着容器技术的发展,还需解决诸如硬件支持、软件定义基础设施集成等挑战。 ... [详细]
  • {moduleinfo:{card_count:[{count_phone:1,count:1}],search_count:[{count_phone:4 ... [详细]
author-avatar
手机用户2602936771
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有