Сценарий
У нас есть веб-приложение. Приложение предназначено для компаний в строительной отрасли, чтобы управлять своими билетами на доставку. Приложение представляет собой современное веб-приложение, построенное на стеке MEAN с хорошо разработанным RESTful API. API используется встроенным веб-порталом и приложением для Android / iOS. API также открыт для интеграции с любой сторонней программой.
Все выглядит хорошо. Все больше и больше клиентов подписываются на использование этого приложения. Одна из наиболее востребованных функций клиентов - это экспорт билетов в определенном формате и импорт в их учетную систему для выставления счетов.
Мы думали, что открытого API было достаточно. Но здесь есть проблемы:
- Практически каждый клиент использует различное бухгалтерское программное обеспечение. Некоторые из них предпочитают XML. Некоторые из них предпочитают CSV. Очень немногие любят JSON.
- Большинство этих учетных систем по-прежнему представляют собой локальное программное обеспечение, установленное в их локальной сети.
- Даже если некоторые клиенты используют одно и то же бухгалтерское программное обеспечение, они могут запросить другой формат с другими полями данных.
- У большинства клиентов нет собственных разработчиков.
Мы решили создать программу CLI, чтобы восполнить этот пробел. Несмотря на то, что мы могли бы встроить любой экспортный формат в клиентский веб-портал с конфигурациями, мы всегда обнаруживаем, что он недостаточно гибкий, чтобы удовлетворить все виды требований клиентов с помощью экспортного формата. Также некоторые заказчики хотят автоматизировать этот процесс в своей локальной сети. Программа CLI идеально подходит для этого сценария. С помощью интерфейса командной строки мы могли бы написать сценарий для автоматизации процесса и настройки формата данных экспорта с сохраненными локально параметрами настройки.
Идеи
- Что такое интерфейс командной строки (CLI)? См. Википедию.
Вот несколько отличных примеров:
- Почему именно 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 доступны несколько популярных библиотек парсеров командной строки:
- Https://github.com/dotnet/command-line-api
- Https://github.com/commandlineparser/commandline
- Https://github.com/natemcmaster/CommandLineUtils
За dotnet / command-line-api стоит очень интересная история. И это как-то связано с natemcmaster / CommandLineUtils. Прочитать рассказ можно здесь. Надеюсь, когда-нибудь его можно будет включить в .NET Core. Прочтите этот документ, он очень полезен при разработке программы CLI.
Кроме того, очень полезно и интересно понимать, как работает командная строка Windows.
В конце концов мы решили использовать https://github.com/natemcmaster/CommandLineUtils. Нам нравится
- API синтаксический дизайн
- Поддержка внедрения зависимостей с помощью универсального хоста
- Поддержка асинхронного выполнения
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, и некоторые важные соглашения о синтаксисе.