Command

Шаблон кастомной команды

Шаблон описания кастомной команды в серверном xml-файле:

<Command Name="MyCustomCommand" Type="CustomCommand" Assembly="TemplateEngine">
  <!-- Элементы команды -->
</Command>

В качестве значения атрибута Assembly указывается имя кастомной сборки, в которой реализована команда. А в атрибуте Type указывается имя класса, описывающего команду.

На сервере команды разделены на две части: инициируемая часть и исполняемая часть. Инициируемая часть представляется классом AbstractCommand, а исполняемая часть - AbstractCommandExecutor. Инициируемая часть используется для парсинга xml-кода, а исполняемая часть - для выполнения бизнес-логики.

Инициируемая часть

Шаблон кода инициируемой части:

CustomCommand.cs
using Common;
using System.Xml;

namespace WorkflowEngine
{
    public class CustomCommand : AbstractCommand
    {
        /*
         * Набор свойств для хранения данных
         * public string PropertyName { get; private set; }
         */
         
        public override void Init(XmlNode node, IWorkflowType workflowType)
        {
            base.Init(node, workflowType);
            
            /*
             * Здесь должен быть парсинг xml-кода,
             * чтобы получить значения тэгов и их атрибутов.
             */
        }

        public override ICommandExecutor CreateExecutor()
        {
            return new CustomCommandExecutor(this);
        }
    }
}

Метод Init() принимает параметр XmlNode node, который соответствует тэгу <Command>, описанному в xml-файле формы. Из объекта node можем получить информацию обо всех вложенных тэгах и их атрибутах. Значения, полученные из xml, сохраняются в публичные свойства, доступ к которым будет у исполняемой части через прямую ссылку на экземпляр класса CustomCommand, переданную в конструктор класса CustomCommandExecutor.

Исполняемая часть

Пример кода исполняемой части:

Метод Execute принимает параметр parameters типа IParameterized, хранящий контекст параметров, общий для всех команд. Из контекста можно получить словарь параметров, который можно использовать для подстановки значений в SQL-запросы вместо переменных.

Как это работает

При старте сервер разбирает серверный xml-файл и создает экземпляр класса CustomCommand для каждого тэга <Command> типа CustomCommand. Когда происходит вызов команды, первым делом выполняется метод CreateExecutor из класса CustomCommand, который создает экземпляр класса CustomCommandExecutor.

Элементы команды

SQL-запросы

Пример команды с указанием текста SQL-запроса:

Инициируемая часть

Для получения значения обязательного тэга в методе Init необходимо использовать статический метод GetRequiredElementValue класса XmlParser:

Вторым параметром (string path) указывается полный путь до тэга, значение которого нужно получить. В третьем параметре (string name) указывается имя объекта, в котором происходит получение значения, в четвертом параметре (object targetObject) - класс объекта, в котором происходит получение значения. Имя объекта и его класс используются в формировании текста сообщения об ошибке. В качестве результата метод возвращает строку. Если элемент отсутствует, будет возвращено исключение типа InvalidXmlException.

Если необходимо получить значение необязательного элемента, то следует использовать метод GetElementValue. Этот метод в случае отсутствия элемента будет возвращать значение по умолчанию, переданное в параметрах метода.

Исполняемая часть

Если команда должна работать с SQL-запросами, то в конструкторе CustomCommandExecutor необходимо получить ссылки на сервисы обеспечивающие работу с базой данных. Для этого используется статический метод GetRequiredService класса ServiceProvider.

Первый сервис - объект типа IDatabaseConnection, который устанавливает соединение с базой данных и выполняет SQL-запрос:

Второй сервис - объект типа ISqlQueryHelper, который помогает построить текст запроса, заменив в нем переменные в круглых скобках {} на нужные значения из словаря параметров:

Для выполнения SQL-запросов в методе Execute используем метод ExecuteQueryAsync() у объекта типа IDatabaseConnection, результатом которого будет объект типа DataTable:

В метод передаются текст SQL-запроса и словарь параметров parameterDictionary. Словарь должен содержать значения исходного словаря parameters.ToDictionary() и дополнительные, необходимы для выполнения запроса.

Внутри метод ExecuteQueryAsync() первым делом заменит все переменные в тексте запроса, а затем, с помощью параметров из parameters.ToDictionary(), построит тексты системных запросов с установкой параметров конфигурации.

При необходимости можно в своем коде сделать замену переменных в тексте запроса, используя сервис ISqlQueryHelper и его метод GetQueryText():

Команды

Пример описания кастомной команды, которая принимает имя другой команды, чтобы использовать ее результат выполнения или вызвать ее для выполнения, передав в нее свои значения:

В примере рассматривается команда AnySqlQueryCommand типа SqlQueryCommand, но это может быть команда любого типа, в том числе и кастомного. Вызов команды AnySqlQueryCommand можно реализовать в xml-файле перед вызовом команды MyCustomCommand, либо перенести в исходный код кастомки.

Инициируемая часть

Для получения значения обязательного атрибута тэга в методе Init необходимо использовать статический метод GetRequiredAttributeValue класса XmlParser:

Вторым параметром (string path) указывается полный путь до тэга, значение атрибута которого нужно получить. Имя атрибута указывается в третьем параметре (string attribute). В данном случае используется константа NAME_ATTRIBUTE, описанная в базовом классе. Если элемент или его атрибут отсутствует, будет возвращено исключение типа InvalidXmlException.

Если необходимо получить значение необязательного атрибута, то следует использовать метод GetAttributeValue. Этот метод в случае отсутствия элемента или его атрибута будет возвращать значение по умолчанию, переданное в параметрах метода.

Исполняемая часть

При работе с командой первым делом нужно получить ее Executor:

Через свойство WorkflowType нашей команды получаем текущий процесс, у которого с помощью метода GetCommandExecutor получаем Executor нужной команды по ее имени.

Результат выполнения команды храниться в свойстве Value:

Так как в примере команда AnyCommandName имеет тип SqlQueryCommand, то ее результат можно привести к типу DataTable, что облегчит его обработку.

Но, прежде чем использовать результат выполнения команды, проверяем значение свойства Value. И если там пусто, то выполняем команду, вызвав метод Execute и передав в него параметры:

Результат выполнения

Результат выполнения команды должен записываться в свойство Value, определенное в наследуемом классе AbstractCommandExecutor:

Результатом команды может быть как скалярное значение, так и список значений.

Скалярное значение напрямую пишется в свойство Value:

И доступно по умолчанию при обращении к команде по имени:

Если необходимо, чтобы команда возвращала несколько значений, то используется Dictionary для хранения пар ключ-значение:

Если обращаться к команде только по имени, то будет возвращаться весь словарь. Чтобы получить конкретное значение из словаря, используется атрибут Parameter, в котором указывается имя ключа:

Логирование

Для возможности добавления записей в журнал событий Windows необходимо получить сервис ILogger, для этого в конструкторе CustomCommandExecutor используем статический метод GetService класса ServiceProvider.

Чтобы записать в журнал событий сообщение об ошибке, необходимо вызвать метод LogError:

Есть разные уровни сообщений для записи в журнал событий:

  • LogCritical() - форматирует и записывает критическое сообщение журнала;

  • LogDebug() - форматирует и записывает в журнал сообщение отладки;

  • LogError() - форматирует и записывает в журнал сообщение об ошибке;

  • LogInformation() - форматирует и записывает в журнал информационное сообщение;

  • LogTrace() - форматирует и записывает в журнал сообщение трассировки;

  • LogWarning() - форматирует и записывает в журнал сообщение с предупреждением.

Last updated