среда, 12 марта 2008 г.

CustomFields "framework", datamodel decorator

Идеологически мой проект является не просто приложением, но расширяемой и настраиваемой платформой. Это громкое звание достигается за счет нескольких функциональных возможностей, заложенных в систему, одной из них является возможность кастомизации (customizing - настройка, расширение функциональности) сущностей приложения. Иными словами говоря, администратор системы в приложении-конфигураторе выбирает сущности, которыми оперирует система и добавляет или удаляет поля, управляет их отображением в UI. Эту функциональность реализует customfields "framework" (опять я использовал это громкое слово).

Функциональность, которую необходимо достичь:
  • добавление и удаление полей сущностей в run-time;
  • кастомизация представлений в различных приложениях и wizard'ах системы (расположение и отображение заложенных в сущность и только что созданных полей);
Поскольку мы были сильно ограничены по срокам, то для реализации требуемой функциональности мы выбрали самое простое решение, которое состоит из:
  • хеш-таблица в базе данных, которая хранит пару имя поля и значение, проиндексированные по уникальному ключу сущности;
  • таблица с настройками, в которой в виде CLOB хранится xaml представлений и список custom полей;
  • административный сервис, который поставляет/сохраняет список custom полей
  • сервис, который поставляет собственно список custom полей по уникальному идентификатору сущности, поставляет/сохраняет UI представления (lose-xaml) в БД.
  • DataModel и CustomFieldsProvider - шлюзы данных для UI.

Как вы поняли, реализация предельно простая. Custom представления хранятся в виде xaml (форматированный xml). Для отображения этого xaml во всех представлениях используется написанный мною примитивный контрол XamlViewer, о которм я уже упоминал при описании локализации форматированного текста.

Итак, самое интересное о чем я вам хочу рассказать - реализация шлюза данных для сущности с custom полями.

Решений данной задачи несколько и, поскольку Blend 1 не поддерживает формат решений VS2008, то для совместной работы с дизайнером, мы остаемся в VS2005 и пишем, используя .Net 3.0, соответственно и решения будут C# 2 специфичные. Это я к тому, что в C# 3 появилась замечательная функциональность (я бы даже сказал синтаксический сахар) - методы расширения. С ними решение было бы просто тривиальным. Но поскольку мы ограничены исполняемой средой, то и будем исходить из этого. Итак, решения:

  • Для каждой сущности написать отдельный класс DataModel, поставляющий только кастомные поля;
  • Или же сделать получение кастомных полей прозрачным для клиента, использующего DataModel - поставщик сущности.

Второй вариант звучит неплохо, но как его реализовать? Обратимся к классикам - в книге GoF описан структурный паттерн Decorator, описание которого дословно звучит: "Динамически возлагает на объект новые функции. Декораторы применяются для расширения имеющейся функциональности и являются гибкой альтернативой порождению подклассов". Бинго! То что нужно. Ведь данное решение позволит добавить к имеющемуся объекту custom поля и мы сможем динамически, основываясь на различных данных (к примеру права доступа), подменять объект его декоратором.


Приступим. Прежде всего опрделимся кого же мы будем декорировать. Если вы помните в посте про DM-V-VM я описал generic-класс SEModel (Single Entity Model). Вот его мы и будем декорировать.


Прежде всего определим интерфейс для CustomFieldsModelDecorator (CFModelDecorator).




На диаграмме вы видите ICFModel - интерфейс для модели, поставляющей custom fields. Custom поля хранятся в специальной коллекции ObservableDictionary - словаре, реализующем databinding-специфичные интерфейсы INotifyPropertyChanged, INotifyCollectionChanged. Готовой реализации в BCL нет, поэтому я написал свою примитивную обертку над Dictionary.

Теперь реализация декоратора:


CFDataModel - generic класс как и SEModel и он также реализует интерфейс ISEModel. Он параметризуется интерфейсом оборачиваемой модели, теми же типами сущности и интерфейсом провайдера, что и оборачиваемая модель, + интерфейсом провайдера custom полей.



Провайдер custom полей предельно прост, вот его интерфейс:



В конструктор декоратора IoC-контейнером инжектируются SEModel и CFProvider. Декоратор для обернутой модели устанавливает режим синхронного обновления и подписывается на событие изменения свойств.


При изменении свойств обернутой модели надо следить за изменением Id.



Основная работа выполняется в методе DoUpdate. Декоратор сначала обновляет custom поля для индексированной по Id сущности, а затем делегирует обновление обернутой DataModel.



Вот собственно и все осталось лишь продемонстрировать применение нашего декоратора.

помещаем его в IoC-контейнер как ISEModel:



и теперь клиент, использующий ISEModel получит наш декоратор совершенно прозрачно, т.е. клиент даже не обязан знать, что он использует ICFModel. Например ViewModel, отображающий сущность Visitor получит декоратор:



и будет работать с ним так, как будто это обычный класс SEDataModel.

Единственное место, где к декоратору обращаются явно - custom xaml, который хранится в БД и создается в конфигураторе. В данном примере его будет поставлять ICustomContentModel. Xaml как я уже говорил отображается XamlViewer:



, а сам xaml может выглядеть вот так:



Все метаданные (список custom полей, xaml с данными - имя поля к которому надо привязываться (binding), порядок расположения полей, их имена в UI, отображаени/скрытие выбранных полей) конфигурируются в специальном приложении - Configurator.

Удачи.

Комментариев нет: