Урок 23. Создание кастомных команд для серверной части
Last updated
Last updated
В уроке продолжим знакомиться с созданием кастомных библиотек для WT-приложений и создадим команду для отправки e-mail на стороне сервера.
Если хотите начать практику с этого урока, то вам необходимо развернуть учебный проект по инструкции в статье Разворачивание проекта.
При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела Ответы прошлого урока. Скопируйте папки Forms, Workflow и Patterns в папку с развернутым проектом, например, в папку D:\WT\Projects\Template\Projects\1. Template.
Инструкция по подключению шаблонов находится по ссылке.
В этом уроке нам понадобятся бинарники, которые мы скачивали при разворачивании серверной части. В уроке так же будем работать с отдельным каталогом для штатных библиотек.
В папке \Template\Projects\1. Template создадим папку Engine, в которой будут храниться исходники кастомных объектов для серверной части.
Запустите Visual Studio 2022 и создайте новый проект из шаблона Class library для .NET или .NET Standart:
В прошлом уроке мы подробно рассмотрели процесс создания нового приложения из шаблона Class library для .NET.
Откроем свойства проекта, вызвав контекстное меню и выбрав пункт Properties. Или нажав комбинацию клавиш Alt+Enter.
На вкладке Build->Events необходимо прописать команду, которая будет выполняться после сборки решения:
В поле Post-build event command line пропишем команду копирования собранного dll-файла из папки проекта в папку, в которую установили серверную часть:
По умолчанию Visual Studio собирает проект в папку:
\Template\Projects\1. Template\Engine\TemplateEngine\TemplateEngine\bin\Debug\netcoreapp3.1
Давайте пересоберем проект, для этого в контекстном меню выберем пункт Build или Rebuild:
Проверьте, что файл TemplateEngine.dll скопировался в папку с установленной серверной частью.
Чтобы запускать сервер из Visual Studio напрямую, в свойствах проекта на вкладке Debug создайте профиль запуска проекта, как мы делали в прошлом уроке. В профиле укажем следующие настройки:
В поле Executable прописываем путь до exe-файла самого web-приложения, размещенного в папке развернутого сервера. В поле Command line argument прописываем --console
аналогично аргументу из файла _start.bat, который используем для запуска серверной части. А в поле Working directory укажем путь до папки, в которой развернут сервер учебного проекта.
Теперь мы можем запустить приложение, нажав клавишу F5 или кнопку на панели инструментов:
Перед запуском проекта не забудьте остановить серверную часть приложения, если она у вас запущена.
В проекте появился новый файл launchSettings.json, в котором будут храниться настройки профилей запуска приложения:
В этом уроке создадим кастомную команду для отправки e-mail на стороне сервера.
Первым делом необходимо описать саму команду, чтобы иметь представление, какие элементы нам понадобятся.
Добавим в серверный xml-файл тэг <Commands>
, который можно разместить перед тэгом <SqlQueries>
. Опишем в нем команду для отправки клиентам писем с поздравлением с днем рождения:
Здесь стоит обратить внимание на несколько моментов.
Во-первых, у тэга <Command>
есть атрибут Assembly
. В нем указывается имя библиотеки, в которой описан класс команды. Так как это кастомная команда, то в атрибуте указано имя кастомной сборки TemplateEngine, которую мы создали ранее.
Во-вторых, в атрибуте Type
указывается имя класса, который описывает логику выполнения команды.
Для отправки писем нам понадобятся адреса клиентов и настройки почтового агента. Поэтому в команде описаны вложенные тэги <EmailSettingsSqlQuery>
и <MessageMailingSqlQuery>
, которые будут содержать sql-запросы на получение необходимых данных. Первый будет предоставлять настройки почтового агента, а второй будет получать из базы адреса клиентов и текст сообщений.
Подключим необходимые библиотеки. Для этого в контекстном меню проекта выберем пункт Add -> Project Reference..., в открывшемся окне на вкладке Browse, где по кнопке Browse... откроем диалоговое окно выбора файла и в папке со штатными бинарниками серверной части найдем файлы WorkflowEngine.dll и Common.dll и добавим их.
Удалим файл Class1.cs и создадим папку Commands, в которую добавим новые классы EmailSendCommand и EmailSendCommandExecutor:
При старте сервера для каждой команды из серверного xml-файла создается один экземпляр класса EmailSendCommand. А каждый раз при обращении к команде для ее выполнения создается экземпляр EmailSendCommandExecutor.
Базовый код кастомной команды EmailSendCommand.cs имеет вид:
Метод void Init(XmlNode, IWorkflowType)
будет выполняться в момент старта сервера и парсинга серверного xml файла. В качестве первого параметра XmlNode node
метод принимает текст описания команды на xml, из которого мы и будем получать необходимые данные. Второй параметр метода IWorkflowType workflowType
содержит процесс, в рамках которого запущен сервер.
Обратите внимание, что для кастомных команд в качестве namespace
указывается WorkflowEngine - это позволяет обращаться к публичным классам штатных библиотек платформы без использования директивы using
для импорта типов.
Базовый код класса исполняемой части имеет вид:
В конструкторе сохраняем контекст команды. Метод Task Execute(IParameterized)
вызывается в момент выполнения команды. В этом методе будет описываться вся бизнес-логика.
Перейдем в файл EmailSendCommand.cs и займемся реализацией метода инициализации команды.
Вспомним описание нашей команды из серверного xml-файла:
Для начала создадим константы с именами вложенных тэгов и текстом сообщения об ошибке:
Создадим два свойства, в которые будем сохранять прочитанные значения тэгов:
Теперь можем в методе Init() прописать код для получения значений тэгов:
Значения тэгов <EmailSettingsSqlQuery>
и <MessageMailingSqlQuery>
по сути просто строки, поэтому для получения их значений используем метод GetRequiredElementValue статического класса XmlParser. Чтобы иметь возможность работать с методами класса XmlParser, добавьте в код using Common
.
Метод GetRequiredElementValue принимает параметры:
Первый параметр XmlNode node
- это сам тэг <Command>
;
Второй параметр string path
- путь до нужного элемента. Так как нам нужно получить значение тэга <Text>
, вложенного в тэг <EmailSettingsSqlQuery>
, то в параметр передаем строку вида "EmailSettingsSqlQuery/Text";
Третий параметр string name
- имя объекта, в котором происходит получение значения. Это имя будет указываться в текст сообщения об ошибке;
Последний параметр object targetObject
- класс объекта, в котором происходит получение значения.
Давайте поставим точку останова на строке вызова базового метода Init и запустим приложение для отладки:
Нажимая клавишу F10, вы можете пройтись по всем строкам метода и посмотреть, что находится в параметрах метода Init и в наших переменных.
Если навести курсор на свойство EmailSettingsSqlQueryText, то увидим всплывающую подсказку с содержимым:
Нажмите на значок лупы, чтобы посмотреть текст содержимого:
Отлично! На этом работу с данным классом можно считать законченной. В дальнейшем будем возвращаться к нему только тогда, когда будет необходимость добавить новый элемент и передавать в него значение через xml-файл.
Перейдем в файл EmailSendCommandExecutor.cs.
Из xml мы получили тексты запросов. Теперь нам понадобятся объекты для подключения к базе данных, чтобы иметь возможность выполнять эти запросы в момент исполнения команды и получать актуальные данные.
Добавим соответствующие глобальные переменные:
А в конструкторе EmailSendCommandExecutor(EmailSendCommand)
пропишем строки:
Через ServiceProvider можно получить различные сервисы, например, контекст пользователя, в котором хранится информация о текущем пользователе, инициировавшем выполнение команды:
ServiceProvider.GetRequiredService(typeof(IUserContext))
Если у вас подсветились ошибки, это значит, что в проекте не подключены дополнительные библиотеки. Чтобы подключить их, необходимо через контекстное меню по пункту Manage NuGet Packages... открыть окно управления пакетами:
На вкладке Installed можно увидеть, что нет ни одного установленного пакета:
Через вкладку Browse можно найти и установить необходимые пакеты:
В поисковой строке введем имя пакета Microsoft.Extensions.DependencyInjection и в списке ниже выберем нужный пакет. В правой части окна нужно выбрать версию пакета. В платформе используется версия 3.1.2 - ее и нужно устанавливать. Нажмите на кнопку Install, чтобы запустить мастер установки пакета.
После успешной установки вернемся в файл EmailSendCommandExecutor.cs. Поставим курсор на место подсвеченной ошибки и нажмем комбинацию клавиш Alt+Enter, чтобы вызвать контекстное меню:
Нажмем клавишу Enter, чтобы применился выбранный вариант замены, который добавит в файл директиву using
с загрузкой необходимого пространства имен.
Прежде чем приступить к реализации бизнес-логики команды, необходимо обеспечить вызов команды на сервере, чтобы иметь возможность отладки кода. Для этого воспользуемся <Scheduler>
, который по расписанию будет вызывать нашу команду.
Скопируйте код и добавьте его в серверный xml-файл перед описанием тэга <Commands>
:
В следующем уроке подробно познакомимся с планировщиком задач, а пока кратко рассмотрим его значение.
Тэг <Task>
представляет конкретную задачу, которая будет выполнять последовательность команд, описанную в тэге <Commands>
по расписанию или при старте сервера. В нашем случае задача будет выполняться при старте сервера, о чем говорит значение атрибута Type
тэга <Condition>
.
Теперь можем перейти к реализации метода Task Execute(IParameterized)
.
В качестве входного параметра метод принимает контекст параметров, которые мы можем передавать в команду при ее вызове в xml-файле и использовать в тексте sql-запросов:
Чтобы иметь возможность работать с такими параметрами, как с привычным словарем, добавим в метод Execute строку:
В xml-файле мы указали текст sql-запроса для получения настроек почтового агента. Давайте посмотрим, как можно выполнять такие запросы в кастомных элементах.
В конструкторе класса мы получали _connection, который позволяет выполнять запросы к базе данных, и _sqlQueryHelper, который помогает построить текст запроса, подставляя в него вместо переменных нужные значения из параметров.
Подключим к проекту библиотеку Exceptions.dll:
Добавим в метод Execute код на выполнения запроса на получение настроек:
В метод GetQueryText передаем текст запроса и словарь параметров. Метод сам произведет подстановку значений вместо переменных.
Ошибку InvalidXmlException, которую мы генерируем, необходимо отлавливать и писать в журнал событий.
Для логирования ошибок необходимо получить соответствующий сервис. Добавим глобальную переменную:
Мы видим, что подсвечивается ошибка. Поставим курсор и нажмем комбинацию клавиш Alt+Enter. В контекстном меню выберем пункт Install package и затем пункт Install with package manager...:
В открывшейся вкладке выберем нужный пакет и версию пакета 3.1.2:
Теперь в конструктор класса добавим строку:
И обернем код в конструкцию try-catch, чтобы отлавливать ошибки при выполнении запросов и обработки данных:
В блоке catch с помощью метода LogError будет писать в журнал событий сообщение об ошибке.
Есть разные уровни сообщений для логирования в журнале событий:
LogCritical - форматирует и записывает критическое сообщение журнала;
LogDebug - форматирует и записывает в журнал сообщение отладки;
LogError - форматирует и записывает в журнал сообщение об ошибке;
LogInformation - форматирует и записывает в журнал информационное сообщение;
LogTrace - форматирует и записывает в журнал сообщение трассировки;
LogWarning - форматирует и записывает в журнал сообщение с предупреждением.
Метод ExecuteQueryAsync возвращает объект типа DataTable, который имеет массив строк. Но так как у нас только одна запись в таблице, то достаточно получать первую строку из этого массива и из значений ее ячеек формировать словарь для хранения настроек.
Создадим метод для разбора строки:
Добавим в метод Execute вызов этого метода:
Создайте файл EmailClient.cs и скопируйте в него код:
Это вспомогательный класс, который будет создавать клиента SmtpClient для отправки писем и генерировать письмо.
Итак, у нас есть настройки и мы можем создать почтового клиента SmtpClient:
Теперь необходимо получать информацию о клиентах, которым хотим отправить письма. Добавим код выполнения соответствующего запроса к базе данных:
Реализуем цикл, в котором будем проходить по строкам полученной таблицы, формировать письма и отправлять их клиентам:
Запустите проект из студии и проверьте, что письма пришли на вашу тестовую почту.
В этом уроке мы рассмотрели возможность создавать кастомные элементы на стороне сервера, чтобы в них реализовать бизнес-логику, которую проще и быстрее описать на языке C#, либо настроить интеграцию со сторонними сервисами.
В приложении Серверная часть собраны статьи о написании кастомных команд и запросов.
В архиве присутствуют xml-файлы форм и серверный xml-файл, исходный код кастомки TemplateEngine, а также бэкап базы данных - с помощью файлов можете проверить себя.