Создание скриптов для консольной команды tadmin Script
Создание скриптов для консольной команды tadmin Script¶
Начиная со сборки платформы 3.6 добавлена возможность создавать скрипты tadmin - специальные классы, которые запускает команда tadmin Script
для выполнения кода .NET. Классы располагаются в сборках расширений, мы рекомендуем использовать сборку Tessa.Extensions.Console
. Скрипты могут получать именованные аргументы, которые используются в классе для параметризации его действий.
Синтаксис команды tadmin Script
доступен в руководстве администратора.
Вместо написания скрипта можно создать собственную команду с произвольными параметрами, как было описано выше, но скрипты значительно проще писать - обычно это класс с единственным методом, в котором разработчик автоматически получает контейнер Unity со всеми зависимостями, способ установки соединения с БД, и открытую сессию с веб-сервисом (опционально). Также скрипты удобны, когда необходимо выполнить некоторые действия при установке обновлений, т.е. для написания миграций.
Клиентские скрипты¶
Клиентские скрипты выполняют логин (открытие сессии) на сервер приложений в соответствии с параметрами, передаваемыми в командной строке. По завершении работы скрипта выполняется закрытие сессии.
Классу скрипта доступны следующие свойства:
-
Result
- код возврата, который получает сценарий, вызывающий командуtadmin
. По умолчанию равен0
. -
Logger
- объект, используемый для логирования с использованием консоли. -
Container
- контейнер Unity, содержащий клиентские зависимости.
В сборке расширений Tessa.Extensions.Console
создадим папку Scripts
, в которой будут располагаться классы со скриптами. Добавим в неё класс с именем NewCard
, который будет содержать скрипт создания структуры карточки с выводом её в окно консоли (или на стандартный вывод, который может быть перенаправлен в файл).
Tip
Вы можете использовать любую структуру папок в любых проектах расширений для определения классов-скриптов. Требования к классу-скрипту в том, чтобы он использовал атрибут [ConsoleScript]
и реализовывал интерфейс IConsoleScript
(а лучше - наследовался от базового класса ConsoleScriptBase
или от одного из его наследников). Использование проекта Tessa.Extensions.Console
и папки Scripts
- это рекомендация.
Класс клиентского скрипта должен быть унаследован от базового класса ClientConsoleScriptBase
. Атрибут [ConsoleScript]
является обязательным для того, чтобы утилита tadmin
определила этот класс как класс скрипта, также в атрибуте указывается имя скрипта, по которому он будет вызван.
using System;
using System.Threading;
using System.Threading.Tasks;
using Tessa.Cards;
using Tessa.Platform.ConsoleApps;
using Unity;
namespace Tessa.Extensions.Console.Scripts
{
[ConsoleScript(nameof(NewCard))]
public class NewCard : ClientConsoleScriptBase
{
protected override async ValueTask ExecuteCoreAsync(CancellationToken cancellationToken)
{
string typeName = this.TryGetParameter("TypeName");
if (string.IsNullOrEmpty(typeName))
{
await this.Logger.ErrorAsync("Pass 'TypeName' parameter for card type name to create.");
this.Result = -1;
return;
}
var request = new CardNewRequest { CardTypeName = typeName };
var response = await this.Container.Resolve<ICardRepository>().NewAsync(request, cancellationToken);
var result = response.ValidationResult.Build();
await this.Logger.LogResultAsync(result);
if (!result.IsSuccessful)
{
this.Result = -2;
return;
}
response.Card.ID = Guid.NewGuid();
await this.Logger.WriteLineAsync(response.Card.ToTypedJson(indented: true));
}
protected override async ValueTask ShowHelpCoreAsync(CancellationToken cancellationToken = default)
{
await this.Logger.WriteLineAsync(
$"Example: {Assembly.GetEntryAssembly()?.GetName().Name} Script {nameof(NewCard)}" +
" -pp:TypeName=Partner");
}
}
}
Скрипт будет вызываться по имени NewCard
. Имена всех скриптов должны быть уникальными, поэтому рассмотрите добавление префикса к имени, например, AbNewCard
. Если вы не укажите имя в атрибуте [ConsoleScript]
, то скрипт можно будет вызвать, указав полное имя типа, а именно Tessa.Extensions.Console.Scripts.NewCard
.
Скрипт должен получить обязательный параметр TypeName
с алиасом типа карточки, который создаётся. Если параметр отсутствует, то будет возвращён код возврата ErrorLevel = -1
(по логике в самом скрипте), что можно будет отследить из файла скрипта .bat
или .sh
.
Пример использования скрипта выводится на экран в методе ShowHelpCoreAsync
, который вызывается при передаче параметра --help
или -help
.
.\tadmin Script NewCard --help
Скомпилируйте и замените файл Tessa.Extensions.Console
в подпапке extensions
внутри папки с tadmin
. Запустите скрипт следующей командой (параметры подключения укажите в соответствии с вашим сервером приложений):
.\tadmin Script NewCard -pp:TypeName=Partner -a:https://localhost/tessa -u:admin -p:admin -q >partner.txt
Tip
Параметр -q
блокирует вывод информационных сообщений, оставляя только ошибки и вывод методов Logger.WriteLineAsync
.
Tip
Имя скрипта NewCard
и имя параметров -pp:Parameter=Value
могут указываться без учёта регистра, в т.ч. при выполнении на Linux.
Если команда выполняется без ошибок, то в текущей папке создаётся файл partner.txt
, в котором указана структура новой карточки “Контрагент”. Откройте файл в блокноте, в секции “Partners” для поля “Name” укажите любую строку (в кавычках), содержащую краткого имени контрагента - это обязательное поле в карточке “Контрагент”. Вы также можете указать другие поля по желанию.
Мы используем этот файл в новом скрипте CreateCard
, создайте класс с таким именем в папке Scripts
.
using System.Threading;
using System.Threading.Tasks;
using Tessa.Cards;
using Tessa.Platform.ConsoleApps;
using Unity;
namespace Tessa.Extensions.Console.Scripts
{
[ConsoleScript(nameof(CreateCard))]
public class CreateCard : ClientConsoleScriptBase
{
protected override async ValueTask ExecuteCoreAsync(CancellationToken cancellationToken)
{
var json = await this.Options.Input.ReadToEndAsync();
var card = new Card().FromTypedJson(json);
var request = new CardStoreRequest { Card = card };
var response = await this.Container.Resolve<ICardRepository>().StoreAsync(request, cancellationToken);
var result = response.ValidationResult.Build();
await this.Logger.LogResultAsync(result);
this.Result = result.IsSuccessful ? 0 : -1;
}
}
}
Команда считывает карточку из стандартного ввода и сохраняет карточку в веб-сервисе от имени указанного пользователя. Посредством вызова type partner.txt
мы выводим карточку из файла partner.txt
на стандартный вывод, который перенаправляется на стандартный ввод команды tadmin Script
.
type partner.txt|.\tadmin Script CreateCard -a:https://localhost/tessa -u:admin -p:admin
Клиентские скрипты с подключением к БД¶
Помимо прямого подключения к веб-сервису та же самая команда может использовать строку подключения в app.json
для прямого подключения к базе данных, чтобы выполнить на БД запросы в обход веб-сервиса.
В проекте Tessa.Extensions.Console
в папке Scripts
создайте класс GetCurrency
. Запрос к базе данных будет выполнять поиск идентификатора карточки валюты по имени, указанному в параметре “Name”. Карточка по найденному идентификатору загружается и выводится на консоль в виде json-структуры.
using System;
using System.Threading;
using System.Threading.Tasks;
using Tessa.Cards;
using Tessa.Platform.ConsoleApps;
using Tessa.Platform.Data;
using Unity;
namespace Tessa.Extensions.Console.Scripts
{
[ConsoleScript(nameof(GetCurrency))]
public class GetCurrency : ClientConsoleScriptBase
{
protected override async ValueTask ExecuteCoreAsync(CancellationToken cancellationToken)
{
string name = this.TryGetParameter("name");
if (string.IsNullOrEmpty(name))
{
await this.Logger.ErrorAsync("Pass 'name' parameter for currency name.");
this.Result = -1;
return;
}
Guid? cardID;
(DbManager db, IQueryBuilderFactory builder) = await this.ConnectDbAsync(cancellationToken);
await using (db)
{
cardID = await db
.SetCommand(
builder
.Select().C("ID")
.From("Currencies").NoLock()
.Where().LowerC("Name").Equals().LowerP("Name")
.Build(),
db.Parameter("Name", name))
.LogCommand()
.ExecuteAsync<Guid?>(cancellationToken);
}
if (!cardID.HasValue)
{
await this.Logger.ErrorAsync("Can't find currency by name: '{0}'", name);
this.Result = -2;
return;
}
var cardRepository = this.Container.Resolve<ICardRepository>();
var response = await cardRepository.GetAsync(new CardGetRequest { CardID = cardID }, cancellationToken);
var result = response.ValidationResult.Build();
await this.Logger.LogResultAsync(result);
if (!result.IsSuccessful)
{
this.Result = -3;
return;
}
await this.Logger.WriteLineAsync(response.Card.ToTypedJson(indented: true));
}
}
}
Note
Рекомендуемым способом решать такую задачу было бы выполнение представления через IViewService
, но для примера выполняется прямой запрос к БД.
Скомпилируйте и замените файл Tessa.Extensions.Console.dll
для замены подпапки extensions
. Выполните следующую команду, чтобы вывести структуру карточки “Валюта” для валюты “RUB” (рубли, поиск без учёта регистра).
.\tadmin Script GetCurrency -pp:Name=RUB -a:https://localhost/tessa -u:admin -p:admin -q
Tip
Укажите параметр командной строки -cs:connectionString
для использования строки подключения с указанным именем вместо строки по умолчанию. Параметр -db:databaseName
может быть указан для изменения имени базы данных в строке подключения.
Если вы укажете несуществующую валюту, то скрипт выведет ошибку и вернёт ненулевой код возврата (в соответствии с бизнес-логикой метода ExecuteCoreAsync
):
.\tadmin Script GetCurrency -pp:Name=RUB111 -a:https://localhost/tessa -u:admin -p:admin -q
Can't find currency by name: 'RUB111'
Серверные скрипты¶
Серверные скрипты имеют полностью инициализированный серверный контейнер IUnityContainer
, который по умолчанию определяет сессию пользователя System
(её можно заменить с использованием метода SessionContext.Create
). При этом не выполняется логина к веб-сервису, и все параметры для логина игнорируются.
Warning
Серверный контейнер содержит только те расширения, которые подключены в extensions.xml
. Для стандартной конфигурации команды tadmin
это платформенные расширения (Server/Shared), серверные Shared-расширения типового решения из сборки Tessa.Extensions.Default.Shared
(как правило, это расширения на метаинформацию) и серверные Shared-расширения проектного решения Tessa.Extensions.Shared
. Для полноценной серверной работы, включая все расширения типового решения (правила доступа, маршруты и др.) мы рекомендуем использовать клиентские скрипты, или же подключать сборки с Server-расширениями в extensions.xml
.
Классу скрипта доступны следующие свойства:
-
Result
- код возврата, который получает сценарий, вызывающий командуtadmin
. По умолчанию равен0
. -
Logger
- объект, используемый для логирования с использованием консоли. -
Container
- контейнер Unity, содержащий серверные зависимости.
В проекте Tessa.Extensions.Console
в папке Scripts
добавьте файл GetCurrencySql
, который будет содержать скрипт, аналогичный GetCurrency
, но выполняемый на прямом подключении к базе данных с ограниченным набором расширений. Серверный скрипт должен наследоваться от базового класса ServerConsoleScriptBase
в дополнение к атрибуту [ConsoleScript]
с именем скрипта.
using System;
using System.Threading;
using System.Threading.Tasks;
using Tessa.Cards;
using Tessa.Platform.ConsoleApps;
using Tessa.Platform.Data;
using Unity;
namespace Tessa.Extensions.Console.Scripts
{
[ConsoleScript(nameof(GetCurrencySql))]
public class GetCurrencySql : ServerConsoleScriptBase
{
protected override async ValueTask ExecuteCoreAsync(CancellationToken cancellationToken)
{
string name = this.TryGetParameter("name");
if (string.IsNullOrEmpty(name))
{
await this.Logger.ErrorAsync("Pass 'name' parameter for currency name.");
this.Result = -1;
return;
}
Guid? cardID;
var dbScope = this.Container.Resolve<IDbScope>();
await using (dbScope.Create())
{
cardID = await dbScope.Db
.SetCommand(
dbScope.BuilderFactory
.Select().C("ID")
.From("Currencies").NoLock()
.Where().LowerC("Name").Equals().LowerP("Name")
.Build(),
dbScope.Db.Parameter("Name", name))
.LogCommand()
.ExecuteAsync<Guid?>(cancellationToken);
}
if (!cardID.HasValue)
{
await this.Logger.ErrorAsync("Can't find currency by name: '{0}'", name);
this.Result = -2;
return;
}
var cardRepository = this.Container.Resolve<ICardRepository>();
var response = await cardRepository.GetAsync(new CardGetRequest { CardID = cardID }, cancellationToken);
var result = response.ValidationResult.Build();
await this.Logger.LogResultAsync(result);
if (!result.IsSuccessful)
{
this.Result = -3;
return;
}
await this.Logger.WriteLineAsync(response.Card.ToTypedJson(indented: true));
}
}
}
Скрипт во много похож на предыдущий, но для выполнения прямых запросов к БД используется объект IDbScope
.
Скомпилируйте и замените файл Tessa.Extensions.Console.dll
в подпапке extensions
. Выполните команду:
.\tadmin Script GetCurrencySql -pp:Name=RUB -q
В окне консоли будет выведена структура карточки “Валюта”.
Скрипты без настроенного контейнера Unity¶
Вы можете унаследовать класс скрипта от базового класса ConsoleScriptBase
, чтобы не выполнялось никаких действий по настройке контейнера Unity
. На основании этого класса реализации ClientConsoleScriptBase
и ServerConsoleScriptBase
предоставляют удобные средства для написания скриптов. Вы можете использовать базовый класс, если необходимо по-особому инициализировать объект контейнера Unity. В общем случае мы не рекомендуем этого делать.
Классу скрипта доступно свойство:
Result
- код возврата, который получает сценарий, вызывающий командуtadmin
. По умолчанию равен0
.
Скрипт по-прежнему использует атрибут [ConsoleScript]
для указания на то, что это скрипт, и для задания его имени. Обратите внимание на то, что переопределяемый метод здесь называется ExecuteAsync
, а не ExecuteCoreAsync
, как в других типах скриптов.
В проекте Tessa.Extensions.Console
в папке Scripts
создайте класс GetCurrencyId
. В этом примере по имени валюты, переданной в параметре -pp:Name=...
, определяется идентификатор карточки запросом к БД.
using System;
using System.Threading;
using System.Threading.Tasks;
using Tessa.Platform.ConsoleApps;
using Tessa.Platform.Data;
using Unity;
namespace Tessa.Extensions.Console.Scripts
{
[ConsoleScript(nameof(GetCurrencyId))]
public class GetCurrencyId : ConsoleScriptBase
{
protected override async ValueTask ExecuteAsync(CancellationToken cancellationToken)
{
var container = new UnityContainer().RegisterConsoleOperationLogger(this.Options);
var logger = container.Resolve<IConsoleLogger>();
string name = this.TryGetParameter("name");
if (string.IsNullOrEmpty(name))
{
await logger.ErrorAsync("Pass 'name' parameter for currency name.");
this.Result = -1;
return;
}
Guid? cardID;
await using (DbManager db = await ConsoleAppHelper.CreateDbManagerAsync(logger, this.Options, cancellationToken))
{
var builder = new QueryBuilderFactory(db.GetDbms());
cardID = await db
.SetCommand(
builder
.Select().C("ID")
.From("Currencies").NoLock()
.Where().LowerC("Name").Equals().LowerP("Name")
.Build(),
db.Parameter("Name", name))
.LogCommand()
.ExecuteAsync<Guid?>(cancellationToken);
}
if (!cardID.HasValue)
{
await logger.ErrorAsync("Can't find currency by name: '{0}'", name);
this.Result = -2;
return;
}
await logger.WriteLineAsync(cardID.Value.ToString());
}
}
}
-
Для получения объекта логирования
IConsoleLogger
вызывается метод-расширениеRegisterConsoleOperationLogger(this.Options)
для контейнераIUnityContainer
. -
Для подключения к БД используется метод
ConsoleAppHelper.CreateDbManagerAsync(logger, this.Options, cancellationToken)
.
Скомпилируйте и замените файл Tessa.Extensions.Console.dll
в подпапке extensions
в папке tadmin
. Выполните команду:
.\tadmin Script GetCurrencyId -pp:Name=RUB -q
На экран консоли выводится идентификатор карточки или сообщение об ошибке, если карточка не найдена. Выполнить команду можно с использованием другой строки подключения и/или базы данных, например:
.\tadmin Script GetCurrencyId -pp:Name=RUB -cs:migration -db:tessa_new -q