Перейти к содержанию

Расширения

Расширения – это способ дополнить стандартное поведение системы в соответствии с бизнес-требованиями разрабатываемого решения. Расширения пишутся как классы с кодом на платформе .NET на языке C#. Классы реализуют определённый интерфейс расширения (например, ITileExtension) и могут наследоваться от абстрактного класса (TileExtension) для упрощения реализации.

Системные требования для разработки расширений

К программному обеспечению компьютера разработчика предъявляются следующие требования:

  • Windows 7 SP1 / 8.1 / 10 / 11

  • Visual Studio 2019 версии 16.8 или старше

  • Если используется другая IDE, помимо Visual Studio, то потребуется также установить .NET SDK 5.0.x последней доступной версии: https://dotnet.microsoft.com/download/dotnet/5.0

Точные версии ПО для вашей сборки платформы перечислены в файле Source\readme.txt в папке со сборкой.

Для Visual Studio допустимо использовать бесплатную версию Community Edition.

Для удобства разработки рекомендуем на том же компьютере развернуть и настроить систему TESSA. Для этого можно использовать бесплатный SQL Server 2012 Express Edition (или более поздний), а также встроенный в Windows IIS.

Содержимое папок Configuration, Source и WebClient SDK добавьте в репозиторий для вашей системы контроля версий. Мы рекомендуем использовать Git или Mercurial.

Tip

При использовании Git на Windows отключите настройку autocrlf, чтобы не изменять переводы строк в файлах конфигурации и других файлах репозитория. Для этого перед клонированием репозитория выполните команду: git config --global core.autocrlf false

Сборки с расширениями

Классы расширений располагаются в специальных сборках с клиентскими или серверными расширениями.

Tessa.Extensions.Server.dll:: сборка с серверными расширениями, которые содержат код, выполняемый только на сервере в веб-сервисе tessa. Серверные расширения имеют доступ к базе данных через объект IDbScope, который можно получить из IoC контейнера Unity или через свойство context.DbScope для некоторых типов расширений (ICardStoreExtension, ICardDeleteExtension), где context – параметр методов расширений. Tessa.Extensions.Client.dll:: сборка с клиентскими расширениями, которые содержат код, выполняемый только на клиентах TessaClient и TessaAdmin (в последнем расширения могут использоваться для предварительного просмотра типов карточек). Клиентские расширения имеют доступ к текущему контексту через статическое свойство UIContext.Current, которое позволяет получить полную информацию по объектам UI в текущей вкладке, будь то вкладка с карточкой, представление или рабочее место.

Note

Также в этой сборке могут содержаться словари ресурсов WPF ResourceDictionary, модели представления ViewModel для реализации паттерна MVVM, элементы управления, поведения и любые другие классы, связанные с взаимодействием с UI на стороне клиента.

Tessa.Extensions.Shared.dll:: сборка с общими вспомогательными классами, используемыми как на клиенте в Tessa.Extensions.Client, так и на сервере в Tessa.Extensions.Server. Ещё в этой сборке можно расположить расширения, задействованные и на клиенте, и на сервере. Примером таких расширений могут послужить универсальные расширения ICardRequestExtension на расчёт дайджеста карточки GetDigest. Tessa.Extensions.Default.(Server|Client|Shared).dll:: сборки со всеми расширениями, обеспечивающими работу типового решения.

Ниже приведены советы по использованию различных видов расширений. Список не является исчерпывающим.

Используйте серверные расширения, если:

  • Требуется выполнить прямое взаимодействие с базой данных, например, прочитать данные из таблицы. Для этого из контейнера Unity следует получить объект IDbScope.

  • Требуется осуществить важную в плане бизнес-логики проверку данных карточки (например, если счёт больше 10000 рублей, то согласование должно идти по другому маршруту).

  • Необходимо выполнить код, который гарантированно не зависит от версии приложения, установленного у клиента.

  • Устанавливаются права на доступ к определённым данным карточки.

Используйте клиентские расширения, если:

  • Необходимо выполнить проверку того, что необходимые поля карточки заполнены.

  • Необходимо проверить бизнес-условие перед тем, как будет отправлен запрос на сервер.

  • Следует расширить плитки на боковой панели, т.е. добавить новую плитку или изменить поведение для существующей (например, скрыть плитку сохранения для определённого типа карточки, если пользователь не администратор и у него нет прав на доступ к карточке). Для этого используются расширения ITileExtension.

  • Требуется расширить поведение определённых элементов управления, например, скрыть определённые поля в карточке в зависимости от доступных разрешений. В этом помогают расширения ICardUIExtension.

  • Требуется обратиться к веб-сервису с клиентской стороны.

Клиентские и серверные расширения регистрируются в классах Registrator, которые можно расположить в папках в соответствии с их назначением. У подобных классов должен быть атрибут [Registrator].

Подробное устройство расширений с множеством примеров можно рассмотреть на основе сборок с расширениями типовой конфигурации. Однако, не рекомендуется изменять код этих сборок, т.к. при обновлении на новую версию платформы изменится также и код расширений, который нужно будет обновить. Такими сборками являются:

  • Tessa.Extensions.Default.Server – серверные расширения для типового решения.

  • Tessa.Extensions.Default.Client – клиентские расширения для типового решения.

  • Tessa.Extensions.Default.Shared – общие вспомогательные методы и расширения, которые доступны как на клиенте, так и на сервере.

Important

Любые изменения в исходном коде сборок типового решения рекомендуется записывать в отдельный документ, который хранить в репозитории проекта вместе с исходниками. Это значительно упростит переход на новую сборку платформы.

Установка расширений

После того, как solution с расширениями Tessa.Extensions.sln будет скомпилирован, в папку Bin/Tessa.Extensions.Client попадают сборки, необходимые для клиента (Tessa.Extensions.Client.dll, Tessa.Extensions.Shared.dll и сборки для типового решения), а в папку Bin/Tessa.Extensions.Server – сборки для сервера (Tessa.Extensions.Server.dll, Tessa.Extensions.Shared.dll и сборки для типового решения).

Для установки обновлённых расширений следует:

  1. Собрать solution Tessa.Extensions.sln в конфигурации Release.

  2. Скопировать все файлы из папки Bin/Tessa.Extensions.Server в папку с веб-сервисом tessa.

  3. Скопировать все файлы из папки Bin/Tessa.Extensions.Client в папку с приложениями TessaAdmin и TessaClient.

  4. Если установка выполняется для распространения обновления через Tessa Applications, то следует опубликовать приложения TessaAdmin и TessaClient.

Цепочки расширений

Расширения выполняются цепочками. Цепочка расширений – это упорядоченная по этапам и по порядковому номеру внутри этапа последовательность вызова одного и того же метода для нескольких расширений одного типа. Например, вызов метода BeforeRequest для расширений ICardNewExtension.

Расширения сначала сортируются по этапу Stage, а потом по номеру Order внутри этапа. Если зарегистрировано несколько расширений одного и того же типа (ICardNewExtension) для одного этапа (AfterPlatform) и одного порядкового номера (Order), то последовательность их выполнения определяется по имени типа с указанием сборки, включающее имя сборки, из которой был загружен объект.

Различают следующие этапы в порядке следования:

  1. Initialize – платформенные расширения на инициализацию (подготовку) данных, которые должны выполняться раньше, чем любые расширения, добавляемые разработчиком. Разработчикам не следует использовать этот этап в своих расширениях.

  2. BeforePlatform – пользовательские расширения, которые пишут разработчики и которые должны исполняться перед платформенными расширениями этапа Platform.

  3. Platform – платформенные расширения, исполнение которых не привязано строго к инициализации или финализации цепочки расширений. Разработчики могут писать расширения на этапе BeforePlatform, которые исполняются перед этапом Platform, или же на этапе AfterPlatform, которые исполняются после этапа Platform. Этот этап разработчикам использовать не следует.

  4. AfterPlatform – пользовательские расширения, которые пишут разработчики и которые должны исполняться после платформенных расширений этапа Platform.

  5. Finalize – платформенные расширения на финализацию (завершение обработки) данных, которые должны идти после любых расширений, добавляемых разработчиком. Разработчикам не следует использовать этот этап в своих расширениях.

Разработчикам рекомендуется писать расширения на этапе AfterPlatform, если нет причин выполнить некоторую работу перед платформенными расширениями (например, для заполнения виртуальных секций или файлов, которые в определённом сценарии обрабатываются платформенными расширениями).

Необработанные исключения в расширениях прерывают цепочку расширений и записываются в лог и в результат валидации context.ValidationResult, содержащий все сообщения, предупреждения и ошибки по ходу выполнения запроса к API карточек. Поэтому для сигнализации о том, что при выполнении расширения что-то пошло не так, рекомендуется записывать сообщения в результат валидации в коде расширений.

Создание расширений

Рассмотрим расширение на импорт:

using System.Threading.Tasks; using Tessa.Cards; using Tessa.Cards.Extensions; using Tessa.Cards.Extensions.Templates; using Tessa.Extensions.Default.Shared.Workflow.KrProcess; using Unity;

public sealed class CardSatelliteImportExtension : CardStoreExtension { public CardSatelliteImportExtension([Dependency(CardRepositoryNames.ExtendedWithoutTransaction)] ICardRepository extendedRepositoryWithoutTransaction) { this.extendedRepositoryWithoutTransaction = extendedRepositoryWithoutTransaction; }

private readonly ICardRepository extendedRepositoryWithoutTransaction;

public override async Task BeforeCommitTransaction(ICardStoreExtensionContext context) { Card satellite; if (context.CardType == null || (satellite = CardSatelliteHelper.TryGetSatelliteCard(context.Request.Card, KrConstants.KrSatelliteInfoKey)) == null) { return; }

satellite.Version = 0; satellite.RemoveAllButChanged(CardStoreMode.Insert, CardStoreMethod.Import);

var request = new CardStoreRequest { Card = satellite, Method = CardStoreMethod.Import }; var response = await this.extendedRepositoryWithoutTransaction.StoreAsync(request);

context.ValidationResult.Add(response.ValidationResult); } }

Это расширение ICardStoreExtension, которое наследуется от базового класса CardStoreExtension для упрощения реализации. В конструкторе расширение запрашивает зависимости у IoC контейнера Unity. В классах Registrator разработчик может зарегистрировать любые классы и интерфейсы в контейнере unityContainer.RegisterType<IMyInterface, MyType>(…), после чего экземпляр расширения получит запрошенные из контейнера объекты.

Переопределение метода BeforeCommitTransaction задаёт, каким образом расширение будет участвовать в цепочке расширений, выполняемой перед коммитом транзакции на карточку. В остальных цепочках расширений этот класс также будет участвовать, но реализации методов BeforeRequest, AfterRequest и пр. по умолчанию пустые, т.е. никаких действий в этих цепочках выполняться не будет.

Расширение должно быть зарегистрировано в контейнере расширений IExtensionContainer, который передаётся методу Register класса Registrator.

using Tessa.Cards; using Tessa.Cards.Extensions; using Tessa.Cards.Extensions.Templates; using Tessa.Extensions; using Tessa.Roles; using Unity; using Unity.Lifetime;

[Registrator] public sealed class Registrator : RegistratorBase { public override void RegisterUnity() { this.UnityContainer.RegisterType<CardSatelliteImportExtension>(new ContainerControlledLifetimeManager()); }

public override void RegisterExtensions(IExtensionContainer extensionContainer) { extensionContainer .RegisterExtension<ICardStoreExtension, CardSatelliteImportExtension>(x => x .WithOrder(ExtensionStage.Platform, 1) .WithUnity(this.UnityContainer) .WhenCardTypes(RoleHelper.PersonalRoleTypeID) .WhenMethod(CardStoreMethod.Import)); } }

Здесь регистрируется класс CardSatelliteImportExtension для типа расширения ICardStoreExtension (на сохранение карточки). Расширение будет выполняться на этапе BeforeCommitTransaction с порядковым номером внутри этапа, равным 1. Экземпляр расширения создаётся из контейнера Unity, в котором он сразу регистрируется с указанием, что параметр ICardRepository (API для работы с карточками) в конструкторе расширения нужно получить с определённым именем (чтобы все методы API выполнялись с пользовательскими расширениями и без транзакций, т.к. они используются внутри транзакции на карточку).

Расширения обычно выполняются следующим образом:

  1. Создаётся объект контекста context, передаваемый во все методы расширения.

  2. Строится упорядоченный список всех расширений определённого типа. Для сохранения карточки это тип расширения ICardStoreExtension.

  3. Расширения фильтруются в соответствие с данными, находящимися в объекте context. Это могут быть типы карточек WhenTypes(“Incoming”, “Outgoing”), чтобы расширение выполнялось только для входящих и исходящих документов. Или это метод взаимодействия с API карточек WhenMethod.

    • Расширения, не попадающие под условие фильтра, исключаются из цепочки и их экземпляры не запрашиваются. Сокращение цепочки расширений вследствие фильтрации положительно сказывается на производительности, но возможности фильтрации ограничены, и для наложения более сложных условий расширение может выполнить код с проверкой в каждом переопределённом методе.

    • Если фильтры не указаны, то расширение соответствующего типа всегда включается в цепочку выполняемых расширений.

  4. Запрашиваются экземпляры всех расширений в цепочке.

    • По умолчанию расширения создаются конструктором по умолчанию. Это можно явно установить вызовом WithDefaultConstructor().

    • Чтобы расширение создавалось ровно один раз для пула приложений через конструктор по умолчанию, следует использовать вызов WithSingleton<TExtension>(), где TExtension – тип регистрируемого расширения. Для примера выше это WithSingleton<DocumentImportExtension>.

    • Если расширение должно запросить из Unity зависимости, то оно может это сделать через конструктор, а тип расширения регистрируется в Unity и выполняется вызов WithUnity(this.UnityContainer). Будет ли расширение создаваться каждый раз или только один раз зависит от ассоциированного LifetimeManager при регистрации в контейнере. Подробнее см. в документации по API Microsoft Unity.

  5. Выполняется метод расширений, соответствующий цепочке (например, метод BeforeCommitTransaction) для каждого экземпляра расширений в порядке, заданном в цепочке.

  6. Далее могут выполняться методы для других цепочек в порядке их вызова системой. Например, после BeforeCommitTransaction транзакция закрывается, блокировка на карточку освобождается и выполняется цепочка AfterRequest.

  7. Выполняется код очистки для каждого экземпляра расширения в цепочке. Если класс расширения реализует интерфейс IDisposable, то для экземпляра такого класса будет выполнен метод Dispose().

Типы расширений

Система предоставляет разработчику несколько типов расширений, клиентских и серверных, сценарии использования которых указаны в таблице. Более подробный перечень примеров и нюансов приведён в последующих разделах при описании соответствующих расширений.

Тип расширения
Назначение Клиент/Сервер Пример
ITileGlobalExtension Создание плиток в боковых панелях. Только клиент. Добавляется плитка “Загрузить из 1С” для типа карточки “Договор” (тип проверяется в делегате Evaluating). При нажатии на плитку выполняется запрос к веб-сервису 1C и к карточке прикладывается xml-файл с данными, загруженными из 1С.
ITileLocalExtension Изменение плиток в боковых панелях перед открытием или после закрытия вкладки с представлением, рабочим местом или карточкой. Также выполняется при обновлении карточки во вкладке, в т.ч. в процессе сохранения. Только клиент. Устанавливается название плитки на сохранение карточки как “Сохранить”, если открыта уже существующая карточки, или как “Сохранить новую”, если открыта только что созданная карточка.
ITilePanelExtension Изменение плиток перед открытием или после закрытия боковой панели. Только клиент. Перед открытием боковой панели запускаем таймер для отображения текущего времени в плитке на правой панели. После закрытия панели останавливаем таймер.
ICardUIExtension Влияние на UI карточки, скрытие контролов. Отслеживание событий открытия / закрытия вкладки с карточкой. Только клиент. В карточке “Договор” ранее зарезервированный номер документа освобождается, если карточка ещё ни разу не была сохранена, а вкладка с карточкой закрывается (что происходит также при штатном закрытии приложения TessaClient).
ICardNewExtension Создание карточки (обычное или по шаблону). Клиент и сервер. Заполняются значения по умолчанию для полей StartDate и EndData в новых строках в секции “Замещения” для карточки “Статическая роль” таким образом, чтобы новые строки соответствовали постоянному замещению (от минимальной возможной даты до максимальной).
ICardStoreExtension Сохранение карточки (первичное или последующее, в т.ч. при завершении задания). Импорт карточки. Восстановление удалённой карточки из корзины. Клиент и сервер. Если завершается задание “Согласование” с вариантом завершения “Согласовать”, то установить поле с состоянием карточки на “Согласовано” перед сохранением.
ICardGetExtension Загрузка карточки при открытии, при сохранении в корзину в процессе удаления, при экспорте на диск. Клиент и сервер. При открытии департамента заполняется виртуальная секция со списком пользователей и текущих заместителей, входящих в состав роли департамента.
ICardGetFileVersionsExtension Запрос на получение списка версий файла из карточки. Клиент и сервер. Для виртуального файла “Лист согласования” вернуть две версии: обычную и версию для печати, контент которой формируется другим образом.
ICardGetFileContentExtension Запрос на получение контента для конкретной версии файла из карточки. Клиент и сервер. Сформировать контент виртуального файла “Лист согласования” в виде HTML-документа, отражающего текущее состояние бизнес-процесса.
ICardDeleteExtension Удаление карточки в корзину или без возможности восстановления. Клиент и сервер. При удалении карточки с процессом согласования также удалять карточку-сателлит.
ICardRequestExtension Универсальные запросы к сервису карточек произвольного назначения. Клиент и сервер. Дайджест типа карточки “Документ” определяется как текстовое представление номера документа.
ICardRequestExtension для RequestType = GetDefaultInformingUsers Запрос к сервису карточек для заполнения списка пользователей в диалоге ознакомления с документом. Клиент и сервер. Серверное расширение для получения списка ознакамливаемых по умолчанию.
ICardMetadataExtension Динамическое изменение типов карточек и метаинформации (например, добавление колонок). Клиент (для предпросмотра) и сервер. Добавление виртуальных секций, вкладок и валидаторов в типы карточек, для которых используется процесс согласования KrProcess.
IMySettingsExtension Модификация поведения диалога “Мои настройки”. Клиент. Отключение редактирования группы настроек, если отмечен определённый флажок в другой настройке. Добавление кнопки в диалог, которая выполняет произвольное действие.
IPluginExtension Расширения, выполняемые по расписанию как плагины Chronos. Сервер (Chronos). Очистка устаревших данных каждый день в нерабочее время. Или действие, выполняемое часто, например, несколько раз в минуту, и требующий инициализации серверного контейнера Unity.
IPlaceholderReplaceExtension Расширения, выполняемые при замене плейсхолдеров. Сервер. Изменение цвета ячейки документа Excel при обработке плейсхолдера, возвращающего плановую дату завершения задания, если дата меньше текущей (т.е. задание просрочено).

Посмотрим, каким образом происходит открытие карточки и какие виды расширений при этом вызываются.

Если уже открытая карточка сохраняется, то порядок выполнения расширений представлен на рисунке ниже.

Расширения для операций

Базовые операции с любыми карточками:

  • New – создание пакета карточки как структуры в памяти, при этом не выполняется запросов в базу для самой карточки.

    Карточка обычно заполняется значениями по умолчанию, иногда заполняется информация по текущему сотруднику (например, он прописывается в поле “Автор” у документов), иногда резервируется номер (если он есть). Резервирование номера приводит к запросу в базу, но такой номер виден, как ещё невыделенный, и администратор может его дерезервировать из карточки последовательности. Для всех карточек, у которых есть резервирование номеров, работают правила доступа, где возможностью выполнить запрос New определяет флаг «Создание карточки».

  • Get – загрузка карточки. Это любое открытие карточки: из представления, двойным кликом из контролов “ссылка” и “список”, обновление уже открытой карточки и т.п.

  • Store – сохранение карточки, причём как первое сохранение (когда в БД выполняются только запросы INSERT), так и любое последующее (когда в карточке есть какие-либо изменения).

  • Delete – удаление карточки. Обычные пользователи могут удалять только в корзину (представление “Удалённые карточки”, из которого их можно восстановить). Администраторы также могут удалять, минуя корзину. Корзина очищается сервисом Chronos, по умолчанию через 30 дней. В Истории действий фиксируются все действия, включая удаление и восстановление карточки.

На каждую из таких операций можно написать расширения, которые выполняются ДО (метод BeforeRequest в классе расширения) или ПОСЛЕ (AfterRequest) самого действия с карточкой.

  • Клиентские расширения толстого клиента. Расширения BeforeRequest выполняются до отправки запроса на сервер, AfterRequest – после отправки.

  • Клиентские расширения легкого клиента. Расширения JavaScript (TypeScript), расширения выполняются аналогично толстому клиенту.

  • Серверные расширения, выполняются и для толстого, и для лёгкого клиентов. Различаются для толстого клиента только расширения на инициализацию (которые выполняются при запуске приложения и что-либо передают с сервера на клиент, например, метаинформацию по карточкам с указанием их полей и настроек контролов, список недоступных типов карточек, чтобы не рисовать для них плитки “создать карточку”, и др.). Серверный BeforeRequest выполняется до платформенной обработки, AfterRequest – после. Платформа “между” BeforeRequest и AfterRequest делает свою типовую работу, например, для запроса Get карточка загружается SELECT-ами, для Store – сохраняется.

Методы расширений AfterRequestFinally, InitializedFinally, ContextInitializedFinally используются для выполнения гарантированных действий по финализации (снятию блокировок, освобождение занятых номеров и т.п.), если действия невозможно выполнить в транзакции. В противном случае для методов AfterRequest (и подобных) возможно прерывание выполнения из-за отмены запроса context.CancellationToken или из-за исключения в других расширениях. Методы выполняются всегда после соответствующих методов без слова “Finally”, независимо от возникших в них ошибок. Также исключение в одном расширении, выброшенное в таком методе, не прерывает цепочку расширений, т.е. для последующих расширений также будет выполнен метод xxxFinally, а исключение от предыдущего метода записано в context.ValidationResult. Следует учитывать, что context.RequestIsSuccessful возвращает true, когда платформенная реализация выполняется без ошибок, но при этом в расширениях AfterRequest (и аналогичных) могла возникнуть ошибка, которая доступна в context.ValidationResult.

Также есть запросы Request, они же “универсальные”. Они могут быть привязаны или не привязаны к какой-то карточке, их задача – выполнить какое-то произвольное действие, про которое платформа (ядро) не знает, и делать дополнительно ничего не будет. Например, это запрос “Пересчёт календаря”, который вызывается с клиента по кнопке, на этот тип запроса подписано единственное серверное расширение, которое проверяет, что если в объекте сессии сейчас администратор (уровень доступа), то можно выполнить хранимую процедуру для расчёта календаря, и либо ничего не вернуть (успех), либо вернуть сообщения об ошибках. Безопасность таких запросов неуниверсальна и различна для каждого запроса, обычно или ограничивается признаком “администратор” в сессии, или запрос выполняет загрузку карточки на сервере с расширениями, и в этом случае работает расчёт прав на Get-запросы (если карточку можно открыть, то запрос будет выполнен).

Расширения и безопасность

Какое-либо значение для безопасности имеют только серверные расширения всех пяти видов (New, Get, Store, Delete, Request). Даже если клиентские расширения что-то дополнительно проверяют, чтобы не выполнять лишних запросов на сервер при недостатке прав, в действительности, сервер всё равно выполняет ту же проверку ещё раз.

При взаимодействии через веб-сервис любые запросы на карточки гарантированно выполняются с расширениями, т.е. что платформенные (в Tessa.dll), что типовые (Tessa.Extensions.Default.Server.dll / **.Shared.dll), что проектные расширения, будут выполнять все проверки на безопасность внутри себя. Платформенная обработка по умолчанию (т.е. НЕ расширения, например, загрузка карточки SELECT-ами) никак не учитывает безопасность, но её можно вызвать только с сервера (т.е. из кода другого расширения веб-сервиса или из Chronos).

Расширения реализуют такой интерфейс: ICardXXXExtension, где вместо XXX подставляем название операции - ICardGetExtension, ICardRequestExtension и др. У них есть одноимённые абстрактные классы CardGetExtension, CardRequestExtension и т.п., методы которых не выполняют полезной работы (реализация по умолчанию), и от которых наследуются уже реальные расширения. Есть абстрактный класс CardNewGetExtension, который реализует интерфейсы и для ICardGetExtension, и ICardNewExtension, это просто чтобы легче было писать расширения с очень похожей логикой.

Для проверки прав по правилам доступа есть следующие основные расширения:

  1. KrCheckCanCreateNewExtension

  2. KrCheckPermissionsGetExtension

  3. KrCheckPermissionsStoreExtension

  4. KrCheckCanDeleteCard

В них вызываются методы статического класса KrPermissionsHelper.

Трассировка цепочки расширений

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

Использование трассировки:

  • значительно упрощает отладку расширений;

  • позволяет найти баги, связанные с порядком выполнения расширений или “нежелательными” расширениями в определённых запросах;

  • помогает проводить профилирование с целью определения наиболее затратных по процессорному времени расширений;

  • обучает разработчиков расширений принципам, в соответствии с которыми определяется порядок выполнения расширений.

Трассировка может выполняться на клиенте и на сервере для следующих типов расширений:

  • IApplicationExtension - расширения, связанные со временем жизни приложения со стороны клиента.

  • ICardNewExtension – создание пакета карточки.

  • ICardStoreExtension – сохранение карточки.

  • ICardStoreTaskExtension – сохранение задания (дополнительная цепочка расширений внутри расширений на сохранение карточки).

  • ICardGetExtension – загрузка карточки.

  • ICardGetFileVersionsExtension – загрузка списка версий для файла карточки.

  • ICardGetFileContentExtension – загрузка контента для версии файла карточки.

  • ICardDeleteExtension – удаление карточки.

  • ICardMetadataExtension - расширения на метаинформацию карточки, выполняются на сервере. Нажмите кнопку “Проверить всё” на вкладке типов в TessaAdmin, чтобы просмотреть трассировку этих расширений.

  • ICardRepairExtension - восстановление структуры сериализованной карточки относительной текущей метаинформации по типам карточек.

  • ICardRequestExtension – универсальное действие с карточкой (запрос Digest’а или загрузка данных из 1С).

  • ICardUIExtension - расширения для карточки со стороны клиента, у которой есть UI (вкладка или диалог).

  • IClientInitializationExtension - расширения на инициализацию приложения со стороны клиента.

  • IFileControlExtension - расширения на элемент управления файлами в UI. Сообщения трассировки добавляются в файл лога в папке TessaClient на уровне логирования Info.

  • IFileExtension - расширения на контекстное меню файла в UI. Сообщения трассировки добавляются в файл лога в папке TessaClient на уровне логирования Info.

  • IFileVersionExtension - расширения на контекстное меню версии в диалоге версий файла в UI. Сообщения трассировки добавляются в файл лога в папке TessaClient на уровне логирования Info.

  • ITileGlobalExtension - расширения на инициализацию боковых панелей при запуске приложения.

  • ITileLocalExtension - расширения на инициализацию и финализацию боковых панелей в пределах вкладки.

  • ITilePanelExtension - расширения на открытие и закрытие боковых панелей.

  • IServerInitializationExtension - расширения на инициализацию приложения со стороны сервера.

Если класс расширения наследуется от одного из абстрактных классов (например, CardNewExtension для интерфейса ICardNewExtension), то реализация методов по умолчанию отключает трассировку этих методов. Поэтому при переопределении методов не следует вызывать реализацию метода из базового класса, например, выполнять base.BeforeRequest(context). В таком случае только переопределённые методы будут показаны в результате трассировки.

Различают следующие режимы трассировки:

  • Off – трассировка отключена (по умолчанию). При этом отсутствуют дополнительные затраты процессорного времени и памяти на выполнение трассировки, поэтому именно такой вариант следует использовать в production-версии приложения.

  • On – включена трассировка всех расширений, выполняемых в цепочке. Такой режим позволяет определить порядок и наличие расширений в цепочках, а также отделить основную цепочку расширений от дополнительной (“вложенной”) при небольших вносимых задержках в выполнении запросов к карточкам. Примером дополнительной цепочки расширений может служить загрузка карточки-сателлита, которая производится из расширения, расположенного в основной цепочке загрузки карточки.

  • Measure – включена трассировка всех расширений с подсчётом времени выполнения для каждого расширения. В дополнение к режиму “On”, этот режим позволяет определить затраты процессорного времени на каждое из выполняемых расширений ценой заметно возросшего времени выполнения запросов к карточкам (пропорционально количеству расширений).

  • Profile – включена трассировка всех расширений с подсчётом времени выполнения для каждого расширения, но для вывода пользователю (в объект context.ValidationResult) сохраняются лишь записи для тех расширений, которые выполнялись бы достаточно большое время (не меньше 5 мс). Если в таком режиме при выполнении действий с карточками не будет выведено сообщений, то каждое расширение было выполнено менее, чем за 5 миллисекунд.

  • Profile:ms - аналогично Profile, где минимальное количество миллисекунд ms указывается явно. Например, Profile:500 отображает все расширения, которые выполняются полсекунды или больше.

    Этот режим позволяет выполнять профилирование времени выполнения расширений и находить слабое по производительности расширение в цепочке, которое можно было бы оптимизировать. Затраты процессорного времени здесь аналогичны затратам в режиме “Measure”. При использовании этого режима рекомендуется перед выполнением запроса перезапускать приложение (если выполняется профилирование клиентских расширений) или веб-сервис / пул приложений (если профилируются серверные расширения) для того, чтобы свести на нет оптимизации исполняющей среды CLR и серверного кэша для выполнения однотипных действий.

Режим трассировки отдельно указывается для клиента и для сервера.

Режим трассировки на сервере определяется настройкой ExtensionTracingMode в конфигурационном файле app.json сервиса web. В качестве значения настройки может быть использован один из приведённых выше режимов (Off, On, Measure, Profile) без учёта регистра символов. Если настройка не указана, то используется режим по умолчанию Off.

При изменении файла app.json необходимо перезапустить пул приложений IIS или веб-сервис Linux после сохранения файла и повторить запрос к серверу (перезапускать клиентское приложение не требуется).

Режим трассировки на клиенте задаётся аналогичной настройкой ExtensionTracingMode в конфигурационном файле app.json приложения TessaClient. Если настройка не указана, то также используется режим по умолчанию Off. После изменения файла требуется перезапустить приложение TessaClient.

Значение, указанное в конфигурационном файле приложения TessaClient, можно переопределить на время одного запуска приложения, указав необязательный ключ командной строки /ExtensionTrace:

TessaClient.exe /ExtensionTrace:Profile

Ключ и его значение нечувствительны к регистру символов и совмещаются с другими ключами:

TessaClient.exe /extensiontrace:profile /u:username /p:password

При включённой трассировке после выполнения каждого расширения в цепочке следующая информация добавляется к результату запроса:

  1. Тип выполненного расширения: ICardGetExtension.

  2. Метод расширения в цепочке: BeforeRequest.

  3. Местоположение расширения: клиентское (или серверное).

  4. Код цепочки: 0x03ae91c6. Уникален в пределах одной и той же цепочки на клиенте или на сервере. Позволяет определить момент начала или окончания дополнительной цепочки расширений. Дополнительная цепочка добавляется в основную цепочку расширением из основной цепочки, сделавшим запрос к сервису карточек. Однако, в пределах одной и той же цепочки код на клиенте и на сервере отличается.

  5. Класс расширения: Tessa.Extensions.Platform.Client.Cards.DecompressCardGetExtension (у этого класса вызывается метод, указанный в п.2). Следует учитывать, что даже если некоторые методы базового класса расширения не переопределяются в дочернем классе, то вызов таких методов всё равно будет выполнен (т.к. метод не выполняет действий по умолчанию, то затрат на выполнение такого метода нет, и время, указанное в п.6, будет равно нулю). Например, если серверное расширение на сохранение карточки ICardStoreExtension переопределяет (override) только метод AfterRequest, то для такого расширения всё равно будут вызваны все методы BeforeRequest, AfterBeginTransaction, BeforeCommitTransaction и AfterRequest, из которых первые 3 будут “пустыми” и выполнятся мгновенно.

  6. Время выполнения для метода расширения в миллисекундах (только для режимов “Measure” и “Profile”): 1 мс.

Режим “Measure”

Режим “Profile”

Расширения замены плейсхолдеров

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

У данного вида расширений есть несколько различных методов, которые выполняются в различные моменты замены плейсхолдеров:

  • Перед формированием документа - вызывается перед тем, как производится замена плейсхолдеров в тексте документа. В данном сценарии можно изменить уже рассчитанные значения строковых плейсхолдеров.

  • После формирования документа - вызывается после того, как документ был полностью сформирован. В данном сценарии можно произвести постобработку документа.

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

  • После формирования таблицы - вызывается после того, как были сформированы все строки данной таблицы. В данном сценарии можно произвести постобработку таблицы документа.

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

  • После формирования строки - вызывается после того, как в строке таблицы были заменены все плейсхолдеры. В данном сценарии можно произвести постобработку строки в документе.

  • Перед заменой плейсхолдера - вызывается перед тем, как производится замена плейсхолдера. В данном сценарии можно изменить результат замены для данного плейсхолдера.

  • После замены плейсхолдера - вызывается после того, как произвелась замена плейсхолдера в документе. В данном сценарии можно произвести постобработку документа после замены плейсхолдера.

Цепочка вызова методов выглядит следующим образом.

В каждом из методов передается параметр context, реализующий интерфейс IPlaceholderReplaceExtensionContext.

Параметр context имеет следующие свойства:

  • ReplacementContext - контекст замены плейсхолдеров. В нем находится информация о заменяемых плейсхолдерах.

  • ValidationResult - результат валидации. В него пишется результат валидации, который вернется на клиент.

  • Info - дополнительная информация, общая для всех сценариев расширений.

  • Table - текущая таблица с информацией обо всех заменяемых значений плейсхолдеров таблицы. Значение актуально только для сценариев формирования таблиц, формирования строк и замены табличных плейсхолдеров.

  • Row - текущая строка с информацией обо всех заменяемых значений плейсхолдеров строки. Значение актуально только для расширений формирования строк и замены табличных плейсхолдеров.

  • Placeholder - текущий заменяемый плейсхолдер. Доступен в сценариях замены плейсхолдеров.

  • PlaceholderValue - значение, на которое будет заменен текущий плейсхолдер. Можно заменить данное значение, чтобы изменить результат плейсхолдера. Доступен при замене плейсхолдеров.

Параметр context имеет следующие методы:

  • GetTextFromPlaceholder(string placeholderText) - метод для получения строки по плейсхолдеру. Учитывает настройки форматирования плейсхолдеров. Если не удалось получить значение плейсхолдера, то возвращает пустую строку.

  • GetValueFromPlaceholder<TType>(string placeholderText) - метод для получения значения заданного типа по плейсхолдеру. Если не удалось получить значение плейсхолдера или найденное им значение имеет другой тип, то возвращает default(TType).

  • GetPlaceholderValue(string placeholderText) - метод для получения объекта PlaceholderValue по плейсхолдеру. Если не удалось получить значение плейсхолдера, то возвращает null. Может быть использтван для установки свойства PlaceholderValue в сценарии Перед заменой плейсхолдера.

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

Для различных типов документов контекст может иметь различный тип и иметь свой набор свойст. Для использования этих свойств сперва необходимо привести контекст к заданному типу. Это можно сделать явно или через вспомогательный метод As<TType>().

Возможны следующие типы контекста:

  • StringPlaceholderReplaceExtensionContext - контекст расширений текстовых документов, заголовка и темы уведомлений и при расчете плейсхолдеров для обычного текста.

  • WordPlaceholderReplaceExtensionContext - контекст расширений документов Word.

  • ExcelPlaceholderReplaceExtensionContext - контекст расширений документов Excel.

Пример. Расширение UI на скрытие элементов управления по условию

Необходимо скрыть контрол при наступлении некоторого булевского условия, которое записывается в поле строковой секции MySection.HideControl. В интерфейсе карточки на это поле может быть повешен контрол CheckBox, тогда в момент клика по нему будет изменено поле. Либо поле можно изменить в коде: card.Sections["MySection"].Fields["HideControl"] = true.

Подпишемся на событие изменения поля, и поменяем видимость контрола с алиасом Control1:

using Tessa.Cards; using Tessa.Platform.Storage; using Tessa.UI.Cards;

model.Card.Sections["MySection"].FieldChanged += (s, e) => { if (e.FieldName == "HideControl") { // модель карточки model : ICardModel, инициализирована снаружи обработчика IControlViewModel control = model.Controls["Control1"];

bool hideControl = (bool)e.FieldValue; control.ControlVisibility = hideControl ? Visibility.Collapsed : Visibility.Visible;

// необходимо скорректировать отступы других контролов в блоке control.Block.Rearrange(); } };

Если возможно, что в форме будет скрыт последний контрол, то блок тоже необходимо скрыть. Для этого вместо вызова Rearrange() на блоке, его выполняют на форме. При этом производится каскадный пересчёт для всех содержащихся в форме блоков.

control.Block.Form.Rearrange();

Аналогичным образом можно скрывать блоки вместе со всеми контролами:

using Tessa.Cards; using Tessa.Platform.Storage; using Tessa.UI.Cards;

model.Card.Sections["MySection"].FieldChanged += (s, e) => { if (e.FieldName == "HideBlock") { IBlockViewModel block = model.Blocks["Block1"];

bool hideBlock = (bool)e.FieldValue; block.BlockVisibility = hideBlock ? Visibility.Collapsed : Visibility.Visible;

block.Form.Rearrange(); } };

Для того, чтобы пересчёт контролов внутри блоков формы делать вручную, вместо block.Form.Rearrange() нужно вызывать метод RearrangeSelf():

// пересчитываем контролы внутри изменённого блока, а затем пересчитываем форму без пересчёта всех дочерних блоков block.Rearrange(); block.Form.RearrangeSelf();

Добавление обработчика события FieldChanged должно выполняться в расширении UI на открытие карточки:

using System.Threading.Tasks; using Tessa.Cards; using Tessa.UI; using Tessa.UI.Cards; using Tessa.UI.Cards.Controls;

namespace Tessa.Extensions.Client.UI { public sealed class CollapseControlExtension : CardUIExtension { public override async Task Initialized(ICardUIExtensionContext context) { Card card = context.Card;

card.Sections["MySection"].FieldChanged += (s, e) => { // код обработчика, приведённый выше ... } } } }

Пусть скрытие контрола будет выполняться только для типа карточки с именем MyType. Тогда регистрируем расширение следующим образом:

extensionContainer .RegisterExtension<ICardUIExtension, CollapseControlExtension>(x => x .WithOrder(ExtensionStage.AfterPlatform) .WithSingleton<CollapseControlExtension>() .WhenCardTypes("MyType"));

Back to top