Разработка расширения для программы DVWebTool

Дополнительным компонентом модуля Web-клиент является программа DVWebTool, которая предоставляет функциям Web-клиента доступ к устройствам и программному обеспечению, установленному на компьютере пользователя Web-клиента. Программа DVWebTool также может быть использована для обработки произвольных задач на пользовательском компьютере.

Программа DVWebTool является приложением для рабочего стола с возможностью расширения функциональности. Расширение DVWebTool представляет собой DLL-файл содержащий ожидаемый класс с веб-методами. Данные методы могут вызываться клиентскими компонентами Web-клиента (есть стандартный метод JSAPI для вызова) или другими способами.

В качестве примера будет разработано расширение, реализующее функцию добавления водяных знаков в PDF-файлы карточки Документ.

Алгоритм работы расширения:
  1. При вызове функции расширения Клиент передаёт идентификаторы PDF файлов карточки Документ (идентификаторы карточек Файл с версиями, а не идентификаторы собственно файлов) и идентификатор самой карточки Документ.

  2. Расширение DVWebTool загружает PDF-файлы на компьютер пользователя с помощью функций, предоставляемых Web-клиентом.

  3. Расширение DVWebTool добавляет в полученные PDF-файлы водяные знаки используя функции сторонней библиотеки для работы с PDF.

  4. Расширение DVWebTool прикрепляет полученные файлы с водяными знаками с помощью функций, предоставляемых Web-клиентом.

    Расширение не должно изменять существующие файлы карточки: полученные PDF-файлы с водяными знаками должны быть добавлены в карточку в качестве дополнительных.

Для реализации функций, указанных в пп. 2 и 4, потребуется разработать расширение Web-клиента с соответствующей функциональностью.

Также для примера необходимо разработать клиентское расширение, которое будет вызывать функции расширения DVWebTool с передачей им идентификаторов PDF-файлов Документа, в которые нужно добавить водяные знаки.

Полный код примера доступен в репозитории на GitHub.

Расширение Web-клиента

Для работы расширения DVWebTool необходимо, чтобы Web-клиент по запросу возвращал файл из карточки Файл с версиями с переданным идентификатором, а также мог прикреплять переданные файлы к карточке Документ.

Требуемая функциональность может быть реализована с помощью расширения Web-клиента.

Ниже приведена часть исходного кода контроллера, реализованного в серверном расширении.

Файл Others\Watermark\WatermarkServerExtension\Controllers\FileOperationsController.cs
public class FileOperationsController : ApiController
{
    IFileService fileService; (1)

    public FileOperationsController(IFileService fileService)
    {
        this.fileService = fileService;
    }

    [HttpGet]
    public HttpResponseMessage GetFile(Guid fileCardID) (2)
    {
        FileReader fileReader = fileService.GetFileReader(fileCardID); (3)

        if (fileReader.FileID == Guid.Empty)
            return new HttpResponseMessage(HttpStatusCode.NotFound);

        HttpResponseMessage response = CreateResponseForFile(fileReader); (4)
        return response;
    }

    [HttpPost]
    public async Task<HttpResponseMessage> AddFile() (5)
    {

        if (!Request.Content.IsMimeMultipartContent()) (6)
        {
            return Request.CreateErrorResponse(HttpStatusCode.UnsupportedMediaType, "Ошибка в формате входных данных");
        }

        string rootPath = CreateAndGetTempFolder(); (7)

        MultipartFormDataStreamProvider provider; (8)

        try
        {
            provider = new MultipartFormDataStreamProvider(rootPath);
        }
        catch (Exception ex)
        {
            return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, $"Ошибка при работе с временными данными: {ex.Message}");
        }

        try
        {
            await Request.Content.ReadAsMultipartAsync(provider); (9)

            if (provider.FileData.Count < 1) (10)
            {
                return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, "В запросе не переданы файлы для получения");
            }

            Guid cardId = GetCardIdFromResponse(provider.FormData); (11)

            List<string> files = await SaveFilesFromResponse(provider.FileData); (12)

            await fileService.AddFilesToCard(cardId, files); (13)
            return Request.CreateResponse(HttpStatusCode.OK);

        }
        catch (Exception e)
        {
            return Request.CreateErrorResponse(HttpStatusCode.InternalServerError, e.Message);
        }
    }

}
1 Отдельный сервис для работы с файлами карточек Документ. Реализация приведена ниже.
2 Возвращает файл из карточки файла с версиями с идентификатором fileCardID.
3 Метод GetFileReader получает файл из карточки файла с версиями с ID fileCardID и передаёт указатель для его чтения.
4 Метод CreateResponseForFile создаёт HTTP-ответ, в который включает данные файла.
5 В карточку добавляются файлы из запроса. Данные добавляемых файлов и идентификатор карточки поступают в теле запроса.
6 Проверяется формат входных данных. Ожидается составной тип.
7 Создаётся временный каталог для оперативного сохранения файлов из запроса
8 Инициализируется доступ к входным данным.
9 Загружаются данные из запроса.
10 Если в запросе нет файлов, возвращается ошибка.
11 Считывается идентификатор карточки.
12 Полученные файлы сохраняются в файловую систему сервера.
13 Полученные файлы прикрепляются к карточке с cardId.

В контроллере используется сервис IFileService, реализация которого приведена ниже. Код дополнительных методов смотрите в полном примере на GitHub.

Файл Others\Watermark\WatermarkServerExtension\Services\FileService.cs
public class FileService : IFileService
{
    private readonly ICurrentObjectContextProvider currentObjectContextProvider;
    private Guid DOCUMENT_CARD_ID = new Guid("B9F7BFD7-7429-455E-A3F1-94FFB569C794");

    public FileService(ICurrentObjectContextProvider currentObjectContextProvider) {
        this.currentObjectContextProvider = currentObjectContextProvider;
    }

    public async System.Threading.Tasks.Task<IEnumerable<Guid>> AddFilesToCard(Guid cardID, List<string> files) (1)
    {
        var document = GetDocumentCard(cardID); (2)

        ILockService lockService = GetLockService();

        if (lockService.IsObjectLockedByAnotherUser(document)) {
            throw new Exception($"Карточка {cardID} заблокирована другим пользователем");
        }

        if (lockService.LockObjectBase(document) == false) {
            throw new Exception($"Не удалось заблокировать карточку {cardID}");
        }

        IDocumentService documentService = GetDocumentService();

        return await System.Threading.Tasks.Task.Run(() =>
        {
            IEnumerable<Guid> documentFileIds;
            try
            {
                IEnumerable<DocumentFile> documentsFiles = documentService.AddAdditionalFiles(document, files);
                documentFileIds = documentsFiles.Select(t => t.FileId);
            }
            catch (Exception ex)
            {
                throw new Exception($"Ошибка при добавлении файлов в карточку {cardID}\n {ex.Message}");
            }
            finally
            {
                lockService.UnlockObject(document);
            }

            return documentFileIds; (3)
        });
    }

    public FileReader GetFileReader(Guid fileCardID)
    {
        IVersionedFileCardService versionedFileCardService = GetVersionedFileCardService();

        VersionedFileCard fileCard = versionedFileCardService.OpenCard(fileCardID); (4)

        Guid fileID = fileCard.CurrentVersion.Id; (5)

        UserSession userSession = GetUserSession();

        if (userSession.FileManager.FileExists(fileID) == false) (6)
            return new FileReader();

        var file = userSession.FileManager.GetFile(fileID); (7)

        return new FileReader() { (8)
            FileID = fileID,
            FileName = file.Name,
            Stream = file.OpenReadStream()
        };
    }

    (9)
}
1 Добавляет файлы из файловой системы в карточку cardID.
2 Получает карточку, к которой прикрепляются файлы.
3 Возвращает идентификаторы добавленных файлов с версиями.
4 Получает указатель для чтения файла из карточки файла с версиями fileCardID.
5 Получает файла карточки с версиями.
6 Если файла нет, возвращает пустой указатель.
7 Запрашивает файл текущей версии.
8 Возвращает указатель для чтения файла.
9 Код вспомогательных функций приведён в полном примере.

Расширение программы DVWebTool

Когда реализовано расширение Web-клиента, предоставляющее и записывающее файлы карточек, может быть реализовано расширение DVWebTool, использующее данные функции.

Расширение DVWebTool представляет собой сборку, в которой реализован интерфейс Docsvision.DVWebTool.WebServices.IServiceManager. Данный интерфейс определяет метод Register, который регистрирует контроллеры с необходимыми функциями во внутреннем веб-сервере DVWebTool, и поле DisplayName с названием расширения.

Ниже приведён код класса, реализующего интерфейс IServiceManager в данном примере.

Файл Others\Watermark\WatermarkWebToolExtension\WatermarkManager.cs
public class WatermarkManager : IServiceManager
{
    public string DisplayName => "Watermark to PDF"; (1)

    public void Register(WebSocketServer server) (2)
    {
        server.AddWebSocketService<WatermarkController>("/Watermark"); (3)
    }
}
1 Название расширения для информации в окне "О программе".
2 Регистрация контроллера расширения.
3 Регистрация контроллера PDFWatermarkController для маршрута Watermark.

В данном примере выполняется регистрация контроллера WatermarkController для обработки запросов, поступающих по пути "/Watermark".

Контроллер, передаваемый в AddWebSocketService, должен быть производным типа Docsvision.DVWebTool.WebServices.BaseService. При его реализации необходимо зарегистрировать в BaseService.actions веб-методы, с помощью которых будут вызываться функции DVWebTool, предоставляемые расширением. Данные методы будут доступны для вызова по протоколу WebSocket по адресу ws://localhost:/%Адрес контроллера%/%Название метода%.

Ниже приведена часть реализации контроллера WatermarkController, содержащего методы обработки входящих запросов на добавление водяного знака.

Файл Others\Watermark\WatermarkWebToolExtension\WatermarkController.cs
public class WatermarkController : BaseService (1)
{
    private readonly ServiceProvider serviceProvider;
    private static readonly Logger Logger = LogManager.GetCurrentClassLogger();

    private string WATERMARK = "Секретно"; (2)

    public WatermarkController()
    {
        serviceProvider = new ServiceProvider();
        Init();
    }

    private void Init() (3)
    {
        actions.Add(nameof(AddWatermarkToFiles), AddWatermarkToFiles);
    }

    private async Task<WebServiceResponse> AddWatermarkToFiles(WebServiceRequest webServiceRequest, JObject data) (4)
    {
        Logger.Info("Получено задание на добавление водяного знака");

        if (data == null)
        {
            return CreateBadResponse("С клиента не переданы данные для работы");
        }

        AddWatermarkRequest request; (5)
        try
        {
            request = data.ToObject<AddWatermarkRequest>();
        }
        catch
        {
            Logger.Error($"Ошибка преобразования полученного сообщения: {data}");
            return CreateBadResponse("Поступивший запрос не соответствует ожидаемому формату");
        }

        string doneInfo;

        try
        {
            doneInfo = await HandleRequest(request);
        }
        catch (Exception ex)
        {
            return CreateBadResponse(ex.Message);
        }

        return CreateEndProcessResponse(request.CardID, $"Водяные знаки добавлены в файлы:<p />{doneInfo}");
    }

    private async Task<string> HandleRequest(AddWatermarkRequest request) (6)
    {
        var connectionService = new ConnectionToWebClient(request.ServerAddress);

        try
        {
            await connectionService.Authentificate();
        }
        catch (Exception ex)
        {
            Logger.Error(ex.Message);
            throw new Exception("Не удалось подключиться к серверу {wc}а");
        }

        List<string> files = new List<string>();

        foreach (var fileId in request.FileIDs) (7)
        {
            try
            {
                string pathToFile = await connectionService.PullFile(fileId);
                files.Add(pathToFile);
            }
            catch (Exception ex)
            {
                Logger.Error(ex.Message);
                throw new Exception($"Не удалось получить из карточки файл с идентификатором {fileId}");
            }
        }

        var watermarkService = new WatermarkService(); (8)

        List<Task<string>> processes = new List<Task<string>>();
        string doneInfo = "";

        foreach (var file in files) (9)
        {
            try
            {
                processes.Add(watermarkService.AddWatermark(file, WATERMARK));
                doneInfo += $" {Path.GetFileName(file)}<p />";
            }
            catch (Exception ex)
            {
                Logger.Error(ex.Message);
                throw new Exception($"Не удалось добавить водяной знак в файл {Path.GetFileName(file)}");
            }
        }

        var filesWithWatermark = await Task.WhenAll(processes);

        try
        {
            await connectionService.PushFiles(request.CardID, filesWithWatermark); (10)
        }
        catch (Exception ex)
        {
            Logger.Error(ex.Message);
            throw new Exception($"Не удалось сохранить файлы в карточке");
        }
        return doneInfo;
    }

    (11)
}
1 Реализация контроллера PDFWatermarkController
2 Текст водяного знака
3 Регистрация методов контроллера PDFWatermarkController. Название метода регистрозависимое.
4 Веб-метод добавления водяного знака. Метод должен принимать два параметра: WebServiceRequest и JObject. Данные передаются в data.
5 Загружаем данные из полученного запроса в модель AddWatermarkRequest.
6 Обработчик запроса на добавление водяного знака. Получает данные запроса. Возвращает строку с названиями файлов, в которые добавлены запросы.
7 Загружаем с Web-клиента файлы, идентификаторы которых переданы в запросе.
8 Сервис для работы с водяными знаками.
9 Добавление водяных знаков в файлы из списка files.
10 Отправка запроса на прикрепление файлов filesWithWatermark к карточке request.CardID.
11 Код вспомогательных функций приведён в полном примере.
Контроллер WatermarkController использует функции двух сервисов:
  • ConnectionToWebClient для получения файлов из карточек Файл с версиями и загрузки файлов в карточки Документ. Данный сервис использует функции, реализованного серверного расширения Web-клиента.

  • WatermarkService для добавления водяных знаков в PDF файлы.

Реализации данных сервисов смотрите в исходных кодах примера на GitHub.

После публикации расширения DVWebTool, зарегистрированный в расширении метод AddWatermarkToFiles контроллера Watermark может быть вызван из клиентского расширения Web-клиента с помощью сервиса DVWebToolConnection.

Клиентское расширение

В качестве примера использования функций расширения Watermark to PDF программы DVWebTool было разработано клиентское расширение, которое выполняет две задачи:

  • Получает из текущей открытой карточки Документа идентификаторы основных файлов формата PDF.

  • Отправляет идентификаторы файлов расширению Watermark to PDF.

Общие требования к реализации клиентских расширений приведены в пункте Расширение возможностей клиентской части Web-клиента.

Прежде всего реализуем сервис, получающий список идентификаторов файлов, в которые нужно добавить водяной знак и передавать его в метод AddWatermarkToFiles расширения Watermark to PDF. Ниже приведён исходный код данного сервиса.

Файл Others\Watermark\WatermarkWebExtension\src\WatermarkService.tsx
export class WatermarkService { (1)

    constructor(private services: $RequestManager & $WebServices & $ApplicationSettings & $MessageBox & $SiteUrl & $CurrentEmployeeId) {
    }

    AddWatermarkToFiles(cardID: string, fileIDs: string[]): Promise<IWebServicesResponse<any>> { (2)

        const data: any = {
            data: { (3)
                cardID: cardID,
                fileIDs: fileIDs,
                userID: this.services.currentEmployeeId, (4)
                serverAddress: this.services.siteUrl (5)
            },
            action: 'AddWatermarkToFiles', (6)
            locale: this.services.applicationSettings.culture.twoLetterISOLanguageName (7)
        };

        return DVWebToolConnection.trySendData("Watermark", data, this.services); (8)
    }
}

export type $WatermarkService = { watermarkService: WatermarkService }; (9)
export const $WatermarkService = serviceName((s: $WatermarkService) => s.watermarkService);
1 Клиентский сервис, предоставляющий доступ к методу добавления водяных знаков, предоставляемому расширением DVWebTool
2 Метод принимает ID карточки и ID её конвертируемых файлов
3 Передача данных, включая следующие два пункта:
4 ID пользователя (для отправки оповещения о завершении процесс).
5 Адрес сервера Web-клиента. DVWebTool должна подключиться к Web-клиенту для получения и сохранения файлов карточки.
6 Название метода, вызываемого из расширения DVWebTool
7 Обязательное для передачи название локали.
8 Вызываем метод AddWatermarkToFiles из контроллера Watermark расширения DVWebTool. Тип DVWebToolConnection предоставляет методы для работы с DVWebTool.
9 Регистрируем сервис WatermarkService.

Сервис $WatermarkService предоставляет единственный метод AddWatermarkToFiles, который вызывает функцию программы DVWebTool с помощью метода DVWebToolConnection.trySendData. При вызове данного метода нужно передать название контроллера и данные, в которых должно быть название вызываемого метода контроллера — в поле action передаваемых данных.

В качестве метода, использующего сервис $WatermarkService реализуем обработчик нажатия кнопки разметки карточки Документ, который будет получать идентификаторы основных PDF-файлов карточки, вызывать метод $WatermarkService.AddWatermarkToFiles и после завершения его работы обновлять содержимое элемента управления Список файлов, или отображать ошибку.

Файл Others\Watermark\WatermarkWebExtension\src\EventHandlers.tsx
export async function addWatermark(sender: LayoutControl) {
   showNotify("Запущен процесс добавления водяных знаков"); (1)

   let cardId = sender.layout.getService($CardId); (2)
   let files = sender.layout.getService($FileService).getFiles();

   let fileIDs = new Array();

   files.forEach((item) => { (3)
      if (item.data.isMain && item.data.name.toLowerCase().endsWith(".pdf")) { (4)
         fileIDs.push(item.data.fileId);
      }
   })

   let watermarkService = sender.layout.getService($WatermarkService); (5)

   let response = await watermarkService.AddWatermarkToFiles(cardId, fileIDs); (6)

   if (response.success == false) {
      showError(response.errorMessage);
   } else {
      showNotify(response.data.message);

      if (layoutManager.cardLayout == null)
         return;

      let currentCardId = sender.layout.getService($CardId);
      if (currentCardId == cardId) {
         let fileList = layoutManager.cardLayout.controls.get<FileListControl>("fileList"); (7)
         await fileList.reloadFromServer();
      }
   }
}
1 Обработчик для события нажатия иконки добавления водяных знаков.
2 Получаем ID текущей карточки и список файлов из элемента FileList.
3 Обрабатываем только основные файлы с расширением .pdf.
4 Получаем из модели files только идентификаторы файлов.
5 Получаем реализованный сервис для работы с водяными знаками.
6 Вызываем функцию добавления водяных знаков для файлов с ID из списка fileIDs.
7 Обновляем список файлов, если есть (возможно уже открыта другая карточка, но в данном случае это не существенно)

Сборка проекта

Сборка серверного расширения Web-клиента и расширения DVWebTool.
  1. Откройте решение Samples.sln.

  2. Соберите проект Other  Watermark  WatermarkServerExtension.

  3. Соберите проект Other  Watermark  WatermarkWebToolExtension.

Сборка клиентской части.
  1. Откройте в командной строке папку Others  Watermark  WatermarkWebExtension.

  2. Выполните команды:

    npm install
    npm update
    npm run build:prod
Публикация компонентов на сервере Web-клиент.
  1. Остановите IIS.

  2. Скопируйте папку SamplesOutput\Site\Content\Modules\WatermarkWebExtension\ в \%WebCinstallDir%\Site\Content\Modules.

  3. Скопируйте папку SamplesOutput\Site\Content\Tools\DVWebTool\Application Files\ в \%WebCinstallDir%\Site\Content\Tools\DVWebTool\.

  4. Скопируйте папку SamplesOutput\Site\Extensions\WatermarkServerExtension в \%WebCinstallDir%\Site\Extensions.

  5. Запустите IIS.

Регистрация расширения DVWebTool на сервере Web-клиент.
  1. Запустите программу \%WebCinstallDir%\Tools\mageui.exe.

  2. Обновите манифест программы:

    1. Нажмите File  Open и выберите файл \%WebCinstallDir%\Site\Content\Tools\DVWebTool\Application FilesDocsvision.DVWebTool.exe.manifest.

    2. Перейдите в раздел Name и в поле Version увеличьте номер сборки. Например, 5.5.5531.0 до 5.5.5531.1.

      Не изменяйте мажорную и минорную версии, и версию исправления.
    3. Перейдите в раздел Files.

    4. Нажмите кнопку Populate.

      В манифест будут добавлены файлы разработанного расширения DVWebTool.

    5. Нажмите File  Save.

      Будет предложено подписать манифест.

    6. Нажмите (три точки) в поле File, выберите файл сертификата \%WebCinstallDir%/DVWebTool.pfx, затем нажмите OK в основном окне подписания манифеста (пароль указывать не нужно). Файл манифеста будет подписан сертификатом DVWebTool.pfx.

  3. Обновите файл развертывания программы:

    1. Нажмите File  Open и выберите файл \%WebCinstallDir%\Site\Content\Tools\DVWebToolDocsvision.DVWebTool.application.

    2. Перейдите в раздел Name и в поле Version увеличьте номер сборки. Например, 5.5.5531.0 до 5.5.5531.1.

      Не изменяйте мажорную и минорную версии, и версию исправления.
    3. Перейдите в раздел Update option и в поле Version введите номер версии, который был получен в разделе Name. Например, 5.5.5531.1.

    4. Перейдите в раздел Application Reference, нажмите кнопку Select Manifest и выберите файл \%WebCinstallDir%\Content\Tools\DVWebTool\Application FilesDocsvision.DVWebTool.exe.manifest. В поле Version будет указана версия, полученная при обновлении файла манифеста.

    5. Нажмите File  Save. Будет предложено подписать файл развёртывания.

    6. Нажмите (три точки) в поле File, выберите файл сертификата \%WebCinstallDir%/DVWebTool.pfx, затем нажмите OK в основном окне подписания (пароль указывать не нужно).

      Файл развертывания будет подписан сертификатом DVWebTool.pfx.

  4. Закройте программу mageui.exe.

  5. Рекомендуется перезапустить IIS.

Проверка примера

Настройте разметку:
  1. В программе Конструктор Web-разметок добавьте элемент Кнопка в любую разметку просмотра карточки Документ.

  2. Укажите для события При щелчке обработчик addWatermark.

  3. Сохраните разметку.

  4. Установите или обновите программу DVWebTool. Cм. пункт Установка и запуск программы DVWebTool руководства пользователя Web-клиент.

  5. Запустите программу DVWebTool. Убедитесь, что программа DVWebTool и Web-клиент будут запущены от имени одного пользователя.

Убедитесь, что все расширения установлены:
  1. В Web-клиенте перейдите в раздел О программе.

    В разделе Подключенные расширения должны быть указаны расширения:
    • WatermarkServerExtension (Сборка %номер сборки%).

    • Watermark to PDF %номер сборки% — web-расширение.

  2. Откройте панель About из меню Docsvision DVWebTool.

    В списке установленных расширений должно быть расширение:
    • Watermark to PDF <версия>.

  3. Откройте для просмотра любую карточку с разметкой, настроенной в п. 1.

  4. Добавьте один или несколько основных файлов с расширением .pdf.

  5. Нажмите на добавленную кнопку с обработчиком addWatermark.

Появится сообщение Запущен процесс добавления водяных знаков — начнётся процесс добавления водяных знаков.

После завершения процедуры появится сообщение Водяные знаки добавлены в файлы: %список PDF-файлов, в которые добавлены водяные знаки%.

В карточку будут добавлены дополнительные файлы с постфиксом _marked, являющиеся копиями оригинальных файлов с добавленным водяным знаком: слово Секретно.

Особенности обновления

При обновлении версии Web-клиента регистрация расширения в программе DVWebTool будет отменена (конфигурационные файлы программы перезаписываются).

После установки новой версии Web-клиента необходимо повторно зарегистрировать расширения DVWebTool на сервере Web-клиент.