Сценарий

У нас есть веб-приложение. Приложение предназначено для компаний в строительной отрасли, чтобы управлять своими билетами на доставку. Приложение представляет собой современное веб-приложение, построенное на стеке MEAN с хорошо разработанным RESTful API. API используется встроенным веб-порталом и приложением для Android / iOS. API также открыт для интеграции с любой сторонней программой.

Все выглядит хорошо. Все больше и больше клиентов подписываются на использование этого приложения. Одна из наиболее востребованных функций клиентов - это экспорт билетов в определенном формате и импорт в их учетную систему для выставления счетов.

Мы думали, что открытого API было достаточно. Но здесь есть проблемы:

  • Практически каждый клиент использует различное бухгалтерское программное обеспечение. Некоторые из них предпочитают XML. Некоторые из них предпочитают CSV. Очень немногие любят JSON.
  • Большинство этих учетных систем по-прежнему представляют собой локальное программное обеспечение, установленное в их локальной сети.
  • Даже если некоторые клиенты используют одно и то же бухгалтерское программное обеспечение, они могут запросить другой формат с другими полями данных.
  • У большинства клиентов нет собственных разработчиков.

Мы решили создать программу CLI, чтобы восполнить этот пробел. Несмотря на то, что мы могли бы встроить любой экспортный формат в клиентский веб-портал с конфигурациями, мы всегда обнаруживаем, что он недостаточно гибкий, чтобы удовлетворить все виды требований клиентов с помощью экспортного формата. Также некоторые заказчики хотят автоматизировать этот процесс в своей локальной сети. Программа CLI идеально подходит для этого сценария. С помощью интерфейса командной строки мы могли бы написать сценарий для автоматизации процесса и настройки формата данных экспорта с сохраненными локально параметрами настройки.

Идеи

  • Что такое интерфейс командной строки (CLI)? См. Википедию.

Вот несколько отличных примеров:

  1. Aws cli
  2. Лазурный клинок
  3. Dotnet cli
  4. Угловой кли
  • Почему именно CLI?

Программы с интерфейсами командной строки, как правило, легче автоматизировать с помощью сценариев.

  • Почему .NET Core?

Очевидно, .NET Core - не единственный вариант создания интерфейса командной строки. И уж точно даже не самый популярный на данный момент. Python или Node.js могут быть лучшим вариантом, если вы с ними знакомы. Но по сравнению с .NET, .NET Core, по крайней мере, выполнил одно из самых важных требований: кроссплатформенность.

Среда разработки

  • Windows 10
  • Visual Studio 2017 Community Edition
  • .NET Core 3.1

Программирование

  • Создайте новый проект и выберите [Консольное приложение (.NET Core)].

  • Отредактируйте файл проекта, щелкнув проект правой кнопкой мыши

В файле проекта измените

to

LangVersion включает async main, вы можете использовать latest

RuntimeIdentifier позволяет публиковать автономные развертывания.

AssemblyName указывает имя выходного exe-файла.

  • Конфигурация

Мы используем файл appsetting.json для сохранения конфигурации в .NET Core.

  • Общий хост

.Net Core 2 представил Generic Host. Хост отвечает за запуск приложения и управление его жизненным циклом.

Очень хороший документ о Generic Host здесь.

Install-Package Microsoft.Extensions.Hosting

Использование универсального хоста, представленного в последней версии .NET Core, значительно упростит настройку ведения журнала, внедрения зависимостей и т. Д.

  • логирование

Serilog - одна из самых популярных библиотек журналов для .NET Core.

Install-Package Serilog.Extensions.Logging
Install-Package Serilog.Settings.Configuration
Install-Package Serilog.Sinks.Console
Install-Package Serilog.Sinks.File

Мы используем appsetting.json для настройки serilog

 .ReadFrom.Configuration(Configuration)

Помещение конфигурации в файл настроек, а не в код, упрощает изменение поведения ведения журнала без изменения кода. В приведенной выше конфигурации для каждого дня используется скользящий файл.

Внутри ConfigureServices он включает ILoggerинтерфейс, доступный для внедрения, предоставляя SerilogLoggerProvider as поставщику ведения журнала.

  • Внедрение зависимости

ConfigureServices passes the HostBuilder IServiceCollection, который можно использовать для настройки внедрения зависимостей. Например,

  • Парсер командной строки

Одна из проблем написания программы CLI - это как разобрать командную строку. Мы всегда можем сами написать парсер с нуля, но это отнимает слишком много времени, хотя это очень интересная задача для кодера. Для .NET Core доступны несколько популярных библиотек парсеров командной строки:

  1. Https://github.com/dotnet/command-line-api
  2. Https://github.com/commandlineparser/commandline
  3. Https://github.com/natemcmaster/CommandLineUtils

За dotnet / command-line-api стоит очень интересная история. И это как-то связано с natemcmaster / CommandLineUtils. Прочитать рассказ можно здесь. Надеюсь, когда-нибудь его можно будет включить в .NET Core. Прочтите этот документ, он очень полезен при разработке программы CLI.

Кроме того, очень полезно и интересно понимать, как работает командная строка Windows.

В конце концов мы решили использовать https://github.com/natemcmaster/CommandLineUtils. Нам нравится

  1. API синтаксический дизайн
  2. Поддержка внедрения зависимостей с помощью универсального хоста
  3. Поддержка асинхронного выполнения
Install-Package McMaster.Extensions.CommandLineUtils
Install-Package McMaster.Extensions.Hosting.CommandLine

await builder.RunCommandLineApplicationAsync<iStradaCmd>(args);

Эта строка кода будет использовать класс iStradaCmd в качестве основной / корневой команды для запуска синтаксического анализатора командной строки и выполнения со встроенным внедрением зависимостей. Он отлично работает с универсальным хостом.

В этой библиотеке есть три важных понятия: команда, опция и аргумент.

Эти концепции пришли из мира unix Структура командной строки.

Параметр - это пара имя / значение, а аргумент - это просто значение. Это просто означает, что параметр может отображаться в командной строке в разных местах, потому что имя может указывать, какой это параметр. С другой стороны, аргумент не имеет имени, поэтому он должен появляться в определенном месте в командной строке.

Библиотека CommandLineUtils поддерживает подкоманду. Например,

git commit -m “initial”

git - это основная / корневая команда, фиксация - это подкоманда.

Библиотека CommandLineUtils использует атрибуты для сопоставления команды / подкоманды с классом и сопоставления параметра и аргумента со свойством класса. Это связывает результаты синтаксического анализа и функциональность. Тип свойства класса команды может быть String, Boolean, Byte, Int16, Int32, Int64, UInt16, UInt32, UInt64, Float, Double, Uri, DateTime, DateTimeOffset, TimeSpan, Array, Enum, HashSet, List, Nullable, Tuple и т. Д. Он сильно зависит от отражения для выполнения сопоставления и синтаксического анализа.

Хороший пример можно найти здесь. [Https://github.com/natemcmaster/CommandLineUtils/blob/master/docs/samples/subcommands/inheritance/Program.cs]

Полный код класса программы:

  • Корневая команда

Для нашей программы CLI мы реализуем две подкоманды для первой версии.

istrada login

istrada list-tickets

login позволяет пользователям входить в систему с их учетными данными. Учетные данные будут сохранены локально как часть его профиля, так что пользователю не нужно будет снова входить в систему в следующий раз.

list-tickets получит билеты из веб-службы.

У нас есть класс iStradaCmd для основной команды, класс LoginCmd для входа в систему подкоманды и класс ListTicketCmd для списка билетов подкоманды. Также все мои классы команд унаследованы от базового класса iStradaCmdBase. Это будет полезно, поскольку весь класс команд может захотеть иметь некоторую общую информацию, такую ​​как профиль пользователя, и некоторые общие функции, такие как процессы HTTP-запроса / ответа.

Вот как выглядит class iStradaCmd :

Атрибуты библиотеки CommandLineUtils в значительной степени самоочевидны.

  • Команда входа в систему

Логин сохранит учетные данные локально в файле, а также проверит учетные данные с помощью API-интерфейса istrada после сохранения, чтобы пользователь знал, ввел ли он правильную учетную информацию или нет.

учетные данные могут быть переданы с помощью параметров -u и -p, или, если они не представлены, программа запросит ввод учетных данных во время выполнения.

Вот как выглядит класс LoginCmd:

Командный вход имеет три варианта: имя пользователя, пароль и постановка. Эти три параметра отображаются в свойствах класса через атрибуты. В методе OnExecute он проверяет, переданы ли имя пользователя или пароль через командную строку. Если нет, он просит пользователя ввести эту информацию, поскольку эти параметры необходимы для продолжения выполнения команды.

Класс Prompt - это помощник из библиотеки, который принимает ввод с консоли. Prompt.GetPasswordAsSecureString будет отображать звездочки, когда пользователь вводит пароль с консоли.

Он записывает профиль пользователя в файл в папке пользователя. Поэтому в следующий раз программа загрузит профиль из файла, и пользователю не нужно каждый раз вводить эту информацию. Пароль зашифрован при сохранении в локальном файле в целях безопасности.

Затем он вызывает API, чтобы попытаться аутентифицироваться.

Вход в систему поддерживает несколько профилей. Вы можете войти в систему как другой пользователь и указать другое имя профиля. Несколько профилей будут сохранены локально. Позже все остальные команды могут передавать параметр proflle, чтобы указать, какой профиль использовать для выполнения этой команды. Если параметр profile не указан в командной строке, по умолчанию будет использоваться профиль «default».

  • Команда List Ticket

Команда List Ticket получает билеты из istrada.

Он принимает параметры start-date и end-date для фильтрации билетов. Если эти два параметра не представлены в командной строке, пользователю будет предложено ввести эти параметры. Затем он форматирует URL-адрес и вызывает API iStrada для получения билетов. Возвращенные данные в формате json. Он вызывает OutputJson для вывода данных. В настоящее время API iStrada всегда возвращает данные в формате json. OutputJson - это метод базового класса команд. Базовый classiStradaCmdBase будет обрабатывать, как выводить данные. FileNameSuffix также является свойством базового класса. FileNameSuffix будет использоваться для форматирования суффикса имени файла при выводе данных в локальный файл.

  • Базовый класс Command

Все классы команд являются производными от базового класса iStradaCmdBase. Базовый класс имеет

  • некоторые общие параметры, применимые к большинству команд, например, Profile, OutputFormat, OutputFile, XSLTFile.
  • некоторые защищенные свойства, которые используются унаследованными классами команд, например, _logger, _httpClientFactory, _console и FileNameSuffix.
  • некоторые общие методы, которые полезны для всех других унаследованных классов, например Encrypt, Decrypt, OnException

Вот как выглядит код:

Когда класс ListTicketCmd получит билеты, он позвонит OutputJson

OutputJson(tickets, “tickets”, “ticket”);

Это связано с тем, что в настоящее время API iStrada может возвращать данные только в формате JSON. OutputJson - это метод в базовом классе, метод будет проверять параметр output-format, параметр по умолчанию - json, если output-format равен xml, он преобразует данные в формат XML, используя JsonConvert.DeserializeXNode библиотеки Json.NET, а затем вызовет метод OutputXml. В методе OutputXml он проверяет, передана ли в командной строке параметр xslt, который указывает на локальный файл XSL. Если да, то будет использовать файл XSL для преобразования данных XML в другой формат (xml, html, csv и т. Д.).

Причина, по которой мы хотим поддерживать преобразование XSLT, заключается в том, что каждый клиент хочет экспортировать данные в свой собственный формат, даже если они могут использовать одно и то же программное обеспечение для бухгалтерского учета поставщика. Поддерживая преобразование XSLT, нам не нужно жестко кодировать преобразование данных в коде, скорее, файл преобразования сохраняется в отдельном файле, который мы можем изменить, не меняя программный код.

Мы также передаем объект XSLTExtension как расширение XSLT в преобразование XSLT. Поскольку .NET реализовал только XSLT 1.0. Мы можем использовать класс XLSTExtension для реализации некоторых функций, которые XSLT 1.0 не поддерживает или которые трудно реализовать. Класс XLSTExtension выглядит так:

После того, как мы преобразовали данные, теперь мы можем вывести данные. Мы могли бы выводить данные на консоль по умолчанию, или, если в командной строке была передана опция output с путем к файлу, мы сохраним данные в файл.

Методы Encrypt и Decrypt предоставляют функцию шифрования пароля профиля пользователя. Он использует AES (Advanced Encryption Standard) с ключом, связанным с именем пользователя ОС. Так что, если вы скопируете этот файл на другой компьютер или используете его под другим пользователем, он не будет работать. Это повышает безопасность, но, строго говоря, это недостаточно хорошая практика.

  • iStradaClient

iStradaClient - это просто класс-оболочка iStrada RESTful API.

Переданный HttpClient создается _httpClientFactory, который был введен инъекцией зависимости из класса команды. Здесь объясняет почему.

  • Код выхода

Когда программа завершается, она возвращает 0 в случае успеха или 1 в случае неудачи. Мы можем определить больше кодов, чтобы указать точные причины сбоя. Этот код называется кодом выхода. Мы можем определить код выхода, как захотим, в программе. Но использование 0 для успеха - широко распространенное соглашение. Если мы запустим программу вручную, вы можете получить код выхода, набрав echo $? в bash или echo %errorlevel% в команде Windows. В Windows / DOS псевдопеременная среды с именем errorlevel хранит код выхода.

Этот код выхода некоторое время важен, когда он возвращается родительскому процессу, и родительский процесс хочет использовать код выхода, чтобы проверить, успешно ли выполняется команда cli. Вот пример на C # в Windows:

Автоматизировать

При наличии инструмента CLI использование сценария для автоматизации этого процесса становится относительно простой задачей. Например, на платформе Windows мы можем написать сценарий PowerShell для автоматизации этого процесса, если клиент хочет запускать эту задачу ежедневно для извлечения билета и автоматического сохранения в локальный файл. Затем используйте планировщик Windows, чтобы настроить расписание для его запуска.

Вот пример

Распределение

Когда многие пользователи используют инструмент CLI, одна проблема будет заключаться в том, как обновить программное обеспечение до самой последней версии. Squirrel.Windows - отличное решение .NET для распространения программного обеспечения для настольных компьютеров Windows. Он без проблем справляется с установкой и обновлением.

Https://github.com/Squirrel/Squirrel.Windows

Запоздалая мысль

Это очень простая программа. Но он указывает на необходимые части для создания программы CLI. Для разработчика интерфейс командной строки более удобен для разработчиков, чем богатый графический интерфейс. Когда мы управляем нашим облачным решением на AWS или Azure или используем какую-либо структуру, такую ​​как .NET Core или Angular 2+, мы стараемся использовать интерфейс командной строки в максимально возможной степени, чтобы избежать использования более удобного портала. Этот опыт определенно поможет нам понять, как работает хорошо спроектированная программа CLI, и некоторые важные соглашения о синтаксисе.