Всем привет. Меня зовут Илья Постников. Я старший разработчик ПО в подразделении «Автоматизации бизнес-процессов» компании Digital Design. В этой статье я хотел бы поделиться опытом кастомизации контролов для web-клиента системы Docsvision (DV). Данная информация будет полезна инженерам, обслуживающим Docsvision и знакомым на начальном уровне с веб-технологиями React/Node.js и .Net MVC.
В Docsvision уже присутствует большой набор контролов для реализации основной логики решений. Все контролы DV ориентированы на работу со справочниками и инфраструктурой DV. Но в реальных компаниях, как правило, уже имеется набор легаси-информационных систем, данные из которых необходимо обрабатывать в DV. Пользователи DV знают про набор шлюзов для интеграций с различными системами при исполнении Workflow-процессов. Но при работе в web-клиенте подобные средства пока еще не реализованы. Не нужно отчаиваться, выход всегда есть!
В моей практике у одного из наших очень крупных заказчиков при внедрении web-клиента Docsvision возникла «интеграционная» проблема. В наличии уже была успешно функционирующая система по сбору заявок и обращений граждан через web-портал. С помощью Docsvision необходимо было реализовать логику обработки зарегистрированных на портале обращений. Имеющийся web-портал, естественно, ничего «не знал» о Docsvision. Равно как и логика web-портала была до поры неизвестна Docsvision. Одной из проблем стала фиксация данных о пользователях портала в карточках Docsvision. Идентификаторы пользователей должны были каким-то образом выбираться оператором и сохраняться в карточках web-клиента Docsvision. Список пользователей портала постоянно обновлялся, поэтому вариант с единовременной миграцией в справочник контрагентов Docsvision не подходил. Необходимо было найти решение этой и схожих с ней проблем.
Вообще, как показал опыт, довольно частая потребность заказчика – выбирать информацию на веб-странице из уже имеющейся внешней системы и сохранять полученные сведения в полях секций DV. При этом контрол, который оперирует данными, не должен выбиваться из общей стилистики страницы. Для примера, пусть таким контролом будет выпадающий список Dropdown из конструктора разметок DV. Из «внешней» системы будем запрашивать:
Числовой идентификатор пользователя.
ФИО пользователя (будет отображаться в выпадающем списке).
После выбора пользователя и сохранения карточки его идентификатор должен быть зафиксирован в указанном поле секции DV.
Наша задача – сделать отдельный контрол, схожий по поведению с Dropdown, который можно будет добавить на страницу в конструкторе разметок.
В вашем случае это может быть любой другой стандартный контрол DV, взятый за основу. Моя цель – показать «стандартный» путь для кастомизации :)
Будем использовать только общедоступные ресурсы для разработки.
Полагаю, что у вас есть установленный лицензионный или триальный web-клиент Docsvision версии не ниже 11.
Программная архитектура будущего контрола
Будем называть контрол по аналогии с контролом-родителем – DropdownOutUser. Для реализации контрола с заданными характеристиками понадобится создать решение из 3 проектов:
Веб-расширение контрола, оформленное как «классический» фронтовый проект для сборки под NodeJS. Понадобится установить NodeJS v14.17.0. В примерах DV уже есть реализация нескольких самостоятельных контролов в каталоге. Все контролы DV выполнены с помощью фреймворка React.js.
Проект серверного расширения – бэк нашего контрола. Будет содержать метод в контроллере для получения ИД и ФИО пользователя из возможной «внешней» системы. Взаимодействие с фронтом будет осуществляться по паттерну MVC.
Расширение для конструктора разметок – это DLL-библиотека, которая добавит описание и настройки DropdownOutUser в конструктор разметок DV (подробно о конструкторе разметок можно прочитать здесь).
Реализация фронтового веб-расширения
Сформируем пока пустой фронтовый проект. Образец можно взять из моего репозитория или из примеров DV. Cкопируйте файлы в свой каталог. Данные репозитории содержат конфигурации для получения конечного js-бандла при помощи сборщика rollup.js.
Настраиваем файл package.json:
Указываем нужные версии двух стандартных библиотек web-клиента в разделе зависимостей. Для 16 версии web-клиента:
Описываем название, версию и описание контрола
Если вы ещё не установили Node.js версии 14.7.0, самое время это сделать. Дистрибутив можно скачать здесь.
Из каталога, в котором располагается файл package.json, из командной строки запускаем команду:
npm install
При этом будет создан каталог node_modules и в него будут загружены все необходимые библиотеки и их зависимости.
Теперь очень важный момент, без которого все усилия были бы тщетны. В каталоге установки веб-клиента (обычно это “C:\Program Files (x86)\Docsvision\WebClient\5.5\Site\Content\App”) на вашем сервере лежат все исходники контролов на языке TypeScript! Остается только найти папку нужного контрола. Исходники нашего Dropdown лежат в каталоге “C:\Program Files (x86)\Docsvision\WebClient\5.5\Site\Content\App\Platform\Controls\Dropdown”.
Копируем файлы Dropdown.tsx и DropdownImpl.tsx в каталог \src\Controls\DropdownOutUser нашего фронтенд-проекта.
Файл Dropdown.tsx содержит классы с описанием параметров, сервисов, событий и логики привязки контрола. DropdownImpl.tsx содержит реакт-компоненту на основе классов. Большинство контролов DV имеют схожую архитектуру.
Во избежание конфликтов и недоразумений переименуем названия классов, содержащих “Dropdown” в “DropdowOutUser” в обоих файлах.
В начале tsx-файлов исходников содержатся привязки к стандартному расположению ресурсов в коде DV. Закомментируйте или удалите импорт ресурсов:
У нас нет кода DV, но есть установленные в пункте 4 библиотеки. Надо «перепривязать» необходимые ресурсы (в основном из @docsvision/webclient). Для быстрого поиска ресурсов в библиотеках рекомендую использовать VSCode или Visual Studio с соответствующими расширениями. В результате должно получиться что-то вроде:
Попробуем собрать получившийся вариант. Для этого из командной строки в основном каталоге расширения (там где лежит package.json) запускаем команду: npm run build. Команда запустит сборщик rollup для js-бандла и gulp для css. При успешной сборке вы должны увидеть картинку наподобие этой:
Фактически мы заново собрали новый вариант контрола Dropdown :)
Если появились сообщения об ошибках, то скорее всего в пункте 7 были неверно указаны привязки.
Здесь и начинается часть, касаемая кастомизации. Напомню, нам надо реализовать запрос к серверу веб-клиента, чтобы получить массив объектов, содержащих Ид и ФИО пользователей.
Добавим во фронт-проект файл \src\Services\DropdownOutUserController.ts с реализацией сервис-контроллера. Можно написать свой вариант запроса через fetch или XMLHttp, но удобнее воспользоваться имеющимся в DV фронтовым сервисом $RequestManager. Он содержит необходимые компоненты для отправки и получения результатов http-запросов GET и POST. Сервис-контроллер будет содержать один единственный метод getUserInfo, возвращающий через Promise-объект массив элементов GenModels.Element[]
GenModels.Element – уже имеющийся тип(интерфейс) в DV.
Можно использовать любой другой подходящий или создать свой тип. Лишь бы он соответствовал данным, получаемым с сервера при запросе.
Для того чтобы воспользоваться созданным сервис-контроллером, его надо не забыть добавить в файле Dropdown.tsx в параметр services:
Импорт добавляем в заголовок файла:
Для того чтобы наш контроллер попал в результирующий js-бандл, надо добавить его описание в начальную точку сборки приложения – файл Index.ts по определенным правилам. Это является гарантией того, что сервис-контроллер будет создан и проинициализирован при загрузке нашего контрола.
И, наконец, осталось указать в коде вызов нашего сервис контроллера. Для этого удобно использовать методы жизненного цикла React-контролов. Самым подходящим является componentDidMount. Он вызывается сразу после загрузки контрола и первоначального рендеринга. Добавим в него вызов нашего метода getUsersInfo:
После возврата и парсинга результатов запроса (это всё делается в сервисе $RequestManager) в users появляется массив с нужными нам Ид и ФИО пользователей. Остается только присвоить новое значение стейту (переменному параметру react) items в методе setState. setState вызовет повторный рендеринг компоненты и контрол отрисуется уже с полученными значениями в выпадающем списке.
Пробуем собрать бандл через команду
npm run build
.Если на этапе 11 не возникло ошибок, полученный бандл DropdownOutUser\DigDes.WC.DropdownOutUser.WE\output\Content\Modules\DigDes.WC.DropdownOutUser.WE\extension.js (а заодно extension.js.map) можно смело скопировать на сервер web-клиента в каталог веб-расширений: “C:\Program Files (x86)\Docsvision\WebClient\5.5\Site\Content\Modules\DigDes.WC.DropdownOutUser.WE”.
Реализация серверного расширения (бэкэнд)
Серверное расширение контрола будет содержать MVC-контроллер с методом, возвращающим Ид и ФИО пользователя из предполагаемой внешней системы. В методе контроллера будем вызывать внутренний сервис, который будет производить запрос данных. Реализация данного сервиса зависит от информационной системы предприятия и в каждом конкретном случае может быть индивидуальной. Это может быть и ADO-клиент для большинства известных БД или .NET клиент для WCF/Web API сервисов и многое другое. Непосредственная разработка этого сервиса в тему данной статьи не входит. В нашей реализации ограничимся возвратом тестового списка с небольшим набором тестовых данных.
Расширение будет исполняться в среде web-клиента DV и должно быть построено по определенным правилам с использованием определенных версий DV-библиотек. Примеры подобных реализаций есть в репозитории DV.
Итак, приступим к реализации.
Для начала создадим в Visual Studio солюшен (DropdownOutUser) и добавим в него проект .Net Framework 4.6 с названием DropdownOutUser.SE (аббревиатура SE – server extension).
Также понадобится набор DLL-библиотек специфических версий. К счастью, у DV есть такой каталог https://github.com/Docsvision/WebClient-Samples/tree/master/Assemblies. Скопируем его в корень нашего решения.
Подключим в проект через “Add Refernces” следующие библиотеки:
Autofac.dll из Assemblies\Autofac (для версий web-клиента 15 и выше)
Autofac.Extras.Ordering.dll из Assemblies\Autofac (для версий web-клиента 15 и выше)
DocsVision.BackOffice.ObjectModel.dll из Assemblies\Docsvision
DocsVision.BackOffice.WebClient.dll из Assemblies\Docsvision
DocsVision.Platform.ObjectManager.dll из Assemblies\Docsvision
DocsVision.Platform.ObjectModel.dll из Assemblies\Docsvision
DocsVision.Platform.WebClient.dll из Assemblies\Docsvision
Docsvision.WebClient.Extensibility.dll из Assemblies\Docsvision
DocsVision.WebClientLibrary.ObjectModel.dll из Assemblies\Docsvision
System.Web.Http.dll из Assemblies\Microsoft
System.Web.Mvc.dll из Assemblies\Microsoft
System.Web.Optimization.dll из Assemblies\Microsoft
Добавим в каталог Controllers нашего проекта файл контроллера DropdownOutUserController.cs. Контроллер наследуется от System.Web.Mvc.Controller.
Создадим описание интерфейса сервиса, получающего данные из внешней ИС в виде файла IDropdowOutUserService.cs. Для удобства расположим его в отдельном каталоге Services.
Обратите внимание, что в качестве элемента возвращаемого списка используется имеющийся в DV класс “Element” из пространства имен Docsvision.BackOffice.WebClient.RadioGroup
Класс полностью соответствует фронтовому ts-интерфейсу GenModels.Element из пункта 9.1. Можно создать свою модель. Главный критерий – соответствие модели на фронте.
Теперь реализуем сервис на основе описанного интерфейса в файле DropdownOutUserService.cs. Единственный метод сервиса GetUsersInfo в нашем случае просто возвращает список “Element” для теста.
В случае наличия «настоящей» ИС рекомендую агрегировать всю логику по аутентификции/авторизации и обработке данных в подобном сервисе.
Вернемся в файл контроллера и создадим конструктор контроллера с одним входным параметром типа System.IServiceProvider. Сохраним его значение в переменной класса _serviceProvider.
Данный провайдер может понадобиться в методах сервисов для получения других сервисов и операций с данными DV. В нашем примере он не используется.
Добавим основной метод GetUserInfo в контроллер:
Для извлечения сервиса используем конструкцию ServiceUtil.GetService из пространства имен Docsvision.Platform.WebClient.Helpers. Полученный результат из метода GetUserInfo возвращаем через обертку CommonResponse. Обертка нужна для того, чтобы web-клиент DV мог стандартным образом обработать успешные или ошибочные результаты запроса. Помимо самого списка пользователей со свойствами ответ будет содержать дополнительную информацию. В принципе, можно этого и не делать, но тогда придётся самим обрабатывать ошибки и десериализовывать результаты запроса на фронте. Успешный ответ инициализируем через метод InitializeSuccess, ошибку через InitializeError. В случае ошибки в браузере появится модальное сообщение DV с текстом, указанном в параметре метода.
Осталось добавить описание расширения в корень текущего проекта DropdownOutUserServerExtension.cs. Описание представляет собой класс, унаследованный от WebClientExtension (пространство имен DocsVision.WebClient.Extensibility).
В версиях web-клиента>=15 потребуется описать созданный нами сервис DropdowOutUserService, а также название и версию расширения. В версиях <15 потребуется описать контроллер и некоторые другие параметры. В DV используется реализация Ioс – контейнера из пакета Autofac.
Теперь при загрузке расширения в среде IIS наш сервис будет гарантированно создан и проинициализирован.
Компилируем проект. Полученную сборку переносим на сервер ВК DV в отдельный каталог. В нашем случае это “C:\Program Files (x86)\Docsvision\WebClient\5.5\Site\Extensions\DropdownOutUser.SE\DropdownOutUser.SE.dll”.
После этого пул приложений в IIS, к которому привязан web-клиент DV, необходимо перезапустить.
Остановка пула и веб-сайта также потребуется, если вы захотите обновить сборку, так как она будет блокирована.
Если после запуска пула/сайта в журнале web-клиента (по умолчанию, C:\Program Files (x86)\Docsvision\WebClient\5.5\Logs\WebClient.log) не было зафиксировано явных ошибок, то мы практически в шаге от успеха :)
Реализация расширения для конструктора разметок (КР)
Для того чтобы наш контрол DropdownOutUser можно было добавлять в нужное место разрабатываемой разметки во встроенном WYSIWUG-редакторе, необходимо создать дополнительную библиотеку – расширение конструктора разметок. Расширение создается по определенным правилам с использованием библиотек DV.
Добавим в наше решение еще один .Net – проект с названием DropdownOutUser.DE (здесь аббревиатура DE-designer extension).
В пункте 2 реализации SE мы уже обращались к каталогу Assemblies. Теперь через “Add References” подключите к проекту следующие сборки:
DocsVision.BackOffice.ObjectModel.dll из Assemblies\Docsvision
DocsVision.BackOffice.WebClient.dll из Assemblies\Docsvision
DocsVision.LayoutEditor.ObjectModel.dll из Assemblies\Docsvision
DocsVision.Platform.ObjectManager.dll из Assemblies\Docsvision
DocsVision.Platform.ObjectModel.dll из Assemblies\Docsvision
DocsVision.Platform.StorageServer.dll из Assemblies\Docsvision
DocsVision.Platform.WebClient.dll из Assemblies\Docsvision
DocsVision.Platform.WebLayoutsDesignerExtension.dll из Assemblies\Docsvision
DocsVision.WebClient.Extensibility.dll из Assemblies\Docsvision
DocsVision.WebClientLibrary.ObjectModel.dll из Assemblies\Docsvision
WebLayoutsDesigner.exe из Assemblies\Docsvision
Xceed.Wpf.Toolkit.Fixed.dll из Assemblies\Others
Добавим файл с ресурсами Resources.resx – для англоязычной и Resources.ru.resx – для русскоязычной версий.
Укажем в них параметры:
ControlName – отображаемое название контрола в КР
ControlGroup – отображаемое название группы контролов в КР
Создадим статический класс с константами. Укажем обязательную константу ClassName с названием контрола. Оно понадобится DV для поиска соответствующего js-класса на фронте.
Добавим файл DropdownOutUserDesignerExtension.cs с описанием расширения. Он содержит класс унаследованный от WebLayoutsDesignerExtension (пространство имен DocsVision.Platform.Tools.LayoutEditor.Extensibility).
В этом классе необходимо описать как необходимые для работы контрола стандартные параметры, так и дополнительные кастомные. Например, это может быть имя внешней БД или сервиса. При загрузке страницы эти свойства будут передаваться в ts-класс, описывающий параметры нашего контрола (DropdownOutUserParams). В нашем проекте таких параметров нет.
Основная декларация контрола происходит в методе GetControlTypeDescriptions. Метод возвращает список описаний контролов.
У нас один контрол, параметры которого задаются в GetControlDescription.
Создаем класс описание через new ControlTypeDescription с указанием имени контрола в качестве параметра конструктора. В свойствах задаем отображаемое в КР имя и группу из пункта 3. В объект PropertyDescriptions добавляем фабрики свойств PropertyFactory для отображаемых в правой части КР параметров.
Среди них стоит отметить GetDataSourceProperty и GetDataFieldPropertу. Методы отвечают за создание свойств для указания секции и поля DV с целью сохранения выбранного значения.
Компилируем сборку. Полученную библиотеку DropdownOutUser.DE.dll и папку с ресурсами копируем в каталог на сервере web-клиента DV: “C:\Program Files (x86)\Docsvision\WebClient\5.5\Plugins\DropdownOutUser.DE”
Запускаем КР и выбираем подходящую разметку, в которую будем добавлять наш контрол. Например, пусть это будет копия стандартной разметки договорного акта. Если всё было сделано верно, в левой части КР должен появиться наш контрол в указанной в пункте 3 группе контролов:
Вставьте драг-н-дропом его в нужный блок разметки. Укажите секцию и поле DV для сохранения выбранного значения в параметрах “Источник данных” и “Поле данных”. Сохраните разметку.
Запуск и проверка в браузере
Запустим созданную разметку в браузере и проверим результат работы средствами разработчика в браузере (F12):
В разделе Network должен появиться вызов метода DropdownOutUser/GetUsersInfo со статусом 200. В разделе Response должны отобразиться полученные из “внешней системы” данные:
Обратите внимание, что помимо json-массива с описанием пользователей присутствуют дополнительные свойства: “success”, “time”, “timestamp” и некоторые другие. Это те самые поля, необходимые для стандартной обработки запросов средствами web-клиента DV.
Мы не добавляли дополнительных css-классов. Визуальное поведение контрола DropdownOutUser ничем не должно отличаться от его “прародителя” – Dropdown.
Спасибо за прочтение!