«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения

05.10.2012, рубрики: .net, devexpress, LOB, xaf, Блог компании DevExpress, Программирование, метки: , , ,

В первой части я рассказывал как «оживить» формы eXpressApp Framework путем добавления простых бизнес правил (контроль данных, управление подсветкой, доступностью и видимостью полей), используя атрибуты в коде бизнес сущностей. В этой части я расскажу о том, как решить эту задачу путем настройки метамодели XAF приложения aka Application Model, а также, собственно, расскажу о том, зачем еще эта метамодель нужна и как она устроена изнутри (забегая вперед, скажу, что она очень напоминает головку лука). Не забуду я и про широкие возможности расширения метамодели разработчиками, ее редактирование конечными пользователями через визуальный редактор Model Editor и многое другое. Я также думаю, что прочитав эту статью, вы возможно обнаружите некоторое сходство метамодели XAF, а также языка ее описания XAFML, со всеми известными CSS и XAML (а также QML, LSML и др.), а также глубинными идеями, заложенными в эти технологии. Всем, кто не побоится познакомиться поближе со столь необычным продуктом отечественного велосипедостроения (кстати, написанным до появления аналогов у Microsoft), прошу пожаловать под кат.

«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения

Атрибуты в коде бизнес сущности vs. Настройки в метамодели

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

  1. [DomainComponent]
  2. public interface IAccount {
  3.     [RuleRequiredField, RuleUniqueValue]
  4.     [RuleRegularExpression(@"^[_a-z0-9-]+(.[_a-z0-9-]+)*@[a-z0-9-]+(.[a-z0-9-]+)*(.[a-z]{2,4})$")]
  5.     string Email { get; set; }
  6.     [FieldSize(25)]
  7.     [ImmediatePostData]
  8.     [Appearance("MarkUnsafePasswordInRed""Len(Password) < 6", FontColor = "Red")]
  9.     string Password { get; set; }
  10. }
  11. [XafDefaultProperty("FullName")]
  12. [DomainComponent, ImageName("BO_Person")]
  13. public interface IPerson {
  14.     string LastName { get; set; }
  15.     string FirstName { get; set; }
  16.     [Calculated("Concat(IsNull(FirstName, ''), ' ', IsNull(LastName, ''))")]
  17.     string FullName { get; }
  18.     DateTime Birthday { get; set; }
  19. }
  20. [DomainComponent]
  21. public interface IOrganization {
  22.     [RuleRequiredField]
  23.     string Name { get; set; }
  24.     [Aggregated]
  25.     IList<IPerson> Staff { get; }
  26.     [RuleRequiredField(TargetCriteria = "Staff.Count > 0")]
  27.     [DataSourceProperty("Staff")/*, DataSourceCriteria("StartsWith(FirstName, '123')")*/]
  28.     [Appearance("ChangeManagerAvailabilityAgainstStaff""Staff.Count = 0", Visibility = ViewItemVisibility.Hide, Context = "DetailView")]
  29.     IPerson Manager { get; set; }
  30. }

Обильное использование атрибутов, которые задают вид и поведение пользовательского интерфейса, прямо в коде бизнес сущности, хотя и помогает значительно повысить скорость разработки в проектах типа «сдал и забыл», в крупных проектах может быть не таким уж и удобным. Также не стоит забывать о сценариях, где любые ссылки на сборки eXpressApp Framework могут быть нежелательны (например, не-XAF проект, который просто использует ту же библиотеку бизнес сущностей, но без всяких там «левых» атрибутов). Ну и наконец, я даже не говорю просто о возможной плохой читаемости и «дурно-пахнусти» кода выше.

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

Тем не менее, я не могу однозначно сказать, что один из этих подходов идеален, а другой запрещен к использованию. Напротив, я убежден, что каждый из этих подходов заслуживает свое место в арсенале разработчика и должен применяться в зависимости от условий проекта (таких как размер, срочность и др.). Так, например, наш основной веб сайт (front-end) является обычным (не-XAF) ASP.NET приложением и использует наши визуальные ASP.NET компоненты для интерфейса и нашу ORM библиотеку eXpress Persistent Objects (XPO) для доступа к данным. При этом библиотека с бизнес сущностями (Customer, Issue, Bug Report, Question, etc.) не имеет ссылок на eXpressApp Framework и не использует XAF-специфичные атрибуты и сущности, хотя и используется в нескольких внутренних приложениях (back-end) написанных на XAF. При этом все настройки представлений этих бизнес сущностей выполнены через метаданные отдельных XAF модулей.

Метамодель приложения или, причем здесь лук…

Таким хранилищем настроек в XAF является модель приложения (Application Model), которая представляет собой слой метаданных, описывающих вид и поведение UI. Технически, это вовсе не один слой, а куча слоев, изменения в которых наслаиваются один на другой (как в головке лука) по принципу суперпозиции. Самый первый слой, так называемое «нулевое» кольцо защиты формируется самим фреймворком следующим образом: фреймворк анализирует наш проект и собирает информацию обо всех подключенных бизнес моделях, контроллерах и командах, редакторах и других сущностях, которые содержатся в текущем модуле, а также во всех его зависимостях. Затем, из собранной информации о типах приложения генерируются, собственно, специальные метаданные, регулирующие все аспекты пользовательского интерфейса, включая представления объектов (Views), системы навигации и команд, локализации и многого другого.
Для лучшего понимания места метамодели в общей архитектуре приложения XAF, приведу небольшую схему:

«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения

Изменения или отличия между слоями в терминах XAF принято называть “model differences”. Отсюда собственно и пошло название “Model.DesignedDiffs.XAFML” для XML файлов модулей, содержащие эти отличия. Отмечу, что вы можете менять содержимое каждого слоя несколькими способами:

1. специальный визуальный редактор – Model Editor;
2. напрямую редактируя XAFML файлы;
3. через программный код.

Суперпозиция всех слоев и будет образовывать итоговую модель приложения:

«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения

Здесь под «итоговой» моделью приложения я подразумеваю то, что получит разработчик в своем коде, когда обратится к какому-то элементу или его свойству. Также, сама по себе метамодель является «ленивой», т.е. суперпозиция вычисляется только по необходимости, например, по запросу от внутреннего API, когда требуется построить какой-то экран пользовательского интерфейса. Здесь важно отметить, что эти вычисления не делаются каждый раз при обращении, а грамотно кэшируются во время исполнения.

Я тут много говорю про слои, но так и детально не объяснил, для чего мы их придумали, ведь за пределами нашего фреймворка так много приложений и технологий, где никаких слоев и в помине нет, взять например XAML. Поэтому ниже перечислю основные причины/требования для создания многослойной метамодели приложения XAF:

1. Возможность гибкой настройки приложений для нескольких платформ;
2. Ускорение разработки за счет конфигурации приложения во время исполнения;
3. Легкая разработка пользовательских модулей, изменяющих стандартное поведение;
4. Возможность централизованной настройки уже развернутого приложения администратором;
5. Возможность индивидуально изменять вид и поведение приложения для конечных пользователей (в том числе динамически, например, на базе прав доступа).

Примеры практического использования метамодели

Думаю достаточно теории, и давайте поработаем с нашей метамоделью на практике. Для этого пересоберем наше приложение и два раза кликнем на Model.DesignedDiffs.xafml файл платформенно-независимого модуля (или вызовем соответствующую команду из контекстного меню в Solution Explorer) нашего прошлого приложения:

«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения

Это приведет к открытию редактора модели (того самого Model Editor), как показано на рисунке выше. В двух словах, этот редактор представляет собой довольно «спартанский» интерфейс с деревом со всеми элементами модели слева и инспектором свойств справа (также есть встроенные средства для быстрого поиска, локализации, объединения слоев модели, но мы пока не будем на них останавливаться здесь – больше можно узнать в документации на английском).
Как раз по этой картинке можно быстро оценить аспекты приложения, контролируемые метамоделью. Кстати, то, что мы видим тут, и есть визуальное представление нулевого слоя модели, сгенерированного XAF по типам, найденным в текущем модуле и его зависимостях.

Представим, что нам вдруг понадобилось чуть-чуть поменять вид одной из наших форм (ICustomer_ListView). Если мы удалим одну колонку, настроим сортировку для другой, поменяем порядок следования колонок и напоследок локализуем наше приложение на русский язык, то при этом в соответствующий слой (Habr.Module/ModelDesignedDiffs.XAFML) будет сохранен вот такой XML, являющийся, по сути, отличием от предыдущего слоя:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <Application Title="HabrDemo" Logo="ExpressAppLogo">
  3.   <Views>
  4.     <ListView Id="ICustomer_ListView">
  5.       <Columns>
  6.         <ColumnInfo Id="Name" SortOrder="Descending" />
  7.         <ColumnInfo Id="Email" Index="1" />
  8.         <ColumnInfo Id="Manager" Index="2" />
  9.         <ColumnInfo Id="Password" Removed="True" />
  10.       </Columns>
  11.     </ListView>
  12.   </Views>
  13. </Application>

*Для экономии места я специально не привожу здесь локализацию, для которой будет создан отдельный XAFML файл.

Если мы запустим наше приложение, то увидим, что наши настройки применились, как и ожидалось (обратите внимание на сортировку и измененный порядок колонок):

«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения

Технически, это означает, что наш код (из стандартных модулей XAF) обратился к метамодели и настроил элементы управления соответственно.

Особенно хочу выделить сценарий с настройкой приложения во время исполнения, а не в Visual Studio. В этом случае все сделанные настройки во время работы приложения попадают в самый последний слой – слой пользовательских настроек. По умолчанию этот последний слой представлен файлом Model.User.XAFML, но по опыту большинство предпочитает файловой системе хранилище в базе данных (скорее всего в будущем мы сделаем это поведением по умолчанию). Благодаря мощным элементам управления DevExpress, настройка приложения не требует каких-то специальных навыков и инструментов и может выполняться в большинстве случаев самими конечными пользователями. Например, пользователь может менять сортировку, группировку фильтры, расположение элементов управления на форме, их размеры и многое другое, используя средства самих визуальных элементов управления. Если хочется более тонкой настройки и вы продвинутый пользователь или разработчик, то к вашим услугам есть Model Editor, также доступный во время исполнения.
Давайте теперь посмотрим, как можно использовать визуальный редактор для быстрой настройки детальных форм. Я думаю, вряд ли это можно продемонстрировать лучше, чем на видео.

Сама идея «резиновых» авто-генерируемых форм была призвана, в основном, облегчить жизнь разработчикам, и она решает две основные задачи:

1. Более удобная и быстрая настройка форм на этапе разработки
2. Более удобное развертывание и поддержка приложения

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

Второе возможно благодаря возможности отдать на откуп настройку форм вашим конечным пользователям или администраторам приложения, так как это задача может быть полностью сделана во время исполнения. Благодаря многослойности модели приложения, вы можете централизованно изменять настройки приложений конечных пользователей, сделав необходимые изменения единожды на уровне администратора (технически в каком-то нижестоящем слое модели).
Чтобы сделать пользовательские настройки частью основного приложения или модуля, вы можете использовать специальное расширение встроенного редактора модели – Model Merge Tool:

«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения«Умные» формы eXpressApp Framework (XAF). Часть 2 — Метамодель UI приложения

Этот инструмент позволяет вам выбрать исходный и целевой слои метамодели и собственно слить их в один.

Еще немного о внутреннем устройстве и расширении метамодели

Выше я показывал кусок XAFML файла, где вы, наверное, заметили в нем Application, Views, ListView и другие элементы, которые представляют собой отдельные элементы метамодели. Внутри, эти элементы реализованы через уже известную вам по первой статье технологию Domain Components (DC) (если быть совсем уж точным, то через ее облегченную версию, которая относится к основной примерно также как Silverlight к полному .NET):

  1. public interface IModelListView : IModelObjectView, IModelView, IModelNode
  2. {
  3.     IModelColumns Columns { get; }
  4.     [DataSourceProperty("Application.Views"), DataSourceCriteria("(AsObjectView Is Not Null) And (AsObjectView.ModelClass Is Not Null) And ('This.ModelClass' Is Not Null) And (AsObjectView.ModelClass.Name = 'This.ModelClass.Name')")]
  5.     IModelDetailView DetailView { get; set; }
  6.     [DataSourceProperty("ModelClass.ListEditorsType")]
  7.     Type EditorType { get; set; }
  8.     IModelSorting Sorting { get; }
  9.     bool UseServerMode { get; set; }
  10.     … и другие свойства, характеризующие формы со списками объектов (List View)
  11. }

Как вы уже, наверное, поняли из главной особенности DC, реальные классы со всей логикой и полями, представляющие модельные элементы, собираются динамически из интерфейсов и связанных с ними специальных классов-логик. Выше я уже упоминал про кэширование вычисленных значений в модели, тут тоже присутствует своего рода кэш для ускорения загрузки приложения, так как сама по себе генерация динамических типов – это не очень быстрая операция (особенно если этих типов сотни и тысячи). Другими словами, сборка динамических типов модели происходит только один раз при первом запуске приложения. Потом, они сохраняются в специальную ModelAssembly.dll сборку (кстати, тоже самое делается и для DC, только попадают они в DcAssembly.dll) рядом с исполняемым файлом. Далее загрузка типов происходит прямиком из этих сборок как обычно. Кстати говоря, не поленитесь и отройте эти две сборки Reflector-ом, чтобы увидеть, во что же превратились все эти интерфейсы:-)

Стандартный набор модельных элементов получается из системных модулей, которые подключаются к новому проекту XAF по умолчанию. Стоит отметить, что слои могут иметь отличающуюся «схему» (как для XML), т.е. доступный набор или схему элементов модели приложения, который напрямую зависит от модулей, используемых для построения слоя. Так, например, у нас есть IModelListView – элемент модели приложения, который определят структуру List View в платформенно-независимых модулях. Есть также платформенно-зависимые элементы IModelListViewWeb и IModelListViewWin, со своим уникальным набором свойств, характеризующих формы со списками объектов. Практически, это означает, что открыв Model Editor для нашего для Web модуля (Habr.Module.Web/ModelDesignedDiffs.XAFML), мы увидим новые элементы и опции, например SaveListViewStateInCookies.

Важно, что эта схема может быть расширена разработчиком по его усмотрению при реализации дополнительной функциональности. Например, для того, чтобы добавить собственную опцию для настройки стандартного элемента List View (или любого другого элемента, чьим кодом вы не владеете), достаточно поместить вот такой код в вашем модуле:

  1. public interface IModelListViewEx {
  2.     string MyCoolOption { get; set; }
  3. }
  4. public override void ExtendModelInterfaces(ModelInterfaceExtenders extenders) {
  5.     base.ExtendModelInterfaces(extenders);
  6.     extenders.Add<IModelListView, IModelListViewEx>();
  7. }

Если же вы владеете кодом элемента, то достаточно просто дописать эту опцию в интерфейс или «подмешать» свое расширение интерфейса явно:

  1. public interface IModelLayoutViewItem : IModelLayoutItem, IModelViewLayoutElement, IModelLayoutElementWithCaptionOptions, IModelNode, ISupportControlAlignment, IModelToolTip, IModelToolTipOptions

Конечно же, разработчик может гибко контролировать и другие аспекты построение модели, такие как генерация нулевого слоя (по сути, вы можете сделать свои генераторы стандартных и пользовательских элементов модели), настройка поведения и отображения в Model Editor и др.

Заключение

В заключение, хочу сказать, что во многом благодаря наличию такого мощного многослойного и динамического слоя метаданных наш фреймворк умеет автоматически строить интерфейс приложения (не могу не вставить модное словечко «UI Scaffolding») для нескольких платформ, используя одну и ту же базу кода (в общем случае это бизнес сущности, контроллеры, команды, пользовательские редакторы и др.). Так в нашем случае имея одну лишь бизнес сущность ICustomer, я в несколько минут создал и настроил приложения для Windows и Web. Также, в общем-то, во многом благодаря возможностям метамодели мы улучшаем жизнь некоторых Windows Forms и Web Forms разработчиков, нивелируя или вообще скрывая для них общеизвестные минусы данных платформ. Таким образом, используя XAF, разработчики могут получать аналоги «плюшек» из XAML-рая, но при этом оставаться на старой доброй платформе, не вкладываясь в изучение ничего нового и не меняя полностью свой подход к разработке, по крайней мере, только так я могу объяснить все еще стабильный интерес к XAF через столько лет.

Не обошлось, конечно, и без минусов, но они касаются только нас, как разработчиков всего этого добра, а не наших пользователей. Дело в том, что мало кто в нашей команде возьмется исправлять баги в ядре движка метамодели, не помыв руки и не помолившись, даже несмотря на тысячи блочных и функциональных тестов – слишком уж велик риск внести ошибку или как минимум случайно ухудшить производительность. Единственное, что не может не радовать душу, так это тот факт, что за более чем 6-ти летнюю историю фреймворка, все детские болезни уже вылечили и багов в области ядра уже почти не осталось:-)

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

Автор: DenisGaravsky

Поделиться новостью

* - обязательные к заполнению поля