Показаны сообщения с ярлыком SCSF. Показать все сообщения
Показаны сообщения с ярлыком SCSF. Показать все сообщения

пятница, 22 августа 2008 г.

Transient Objects Injection in CAB

Если вы испытывали затруднение с тем, как внедрять не singleton-зависимости в CAB, то вот вам решение:

  • TransientContainerService;
  • TransientDependencyStrategy;
  • TransientDependencyAttribute;
Дело в том, что в IoC-контейнере CAB зарегистрировать тип/объект можно только как singleton, но иногда такое поведение не устраивает, а простых средств это ограничение обойти я не нашел.

Зато такие возможности есть в любом полноценном IoC-контейнере, например в Unity, который я и использовал. В результате мы имеем 2 IoC-контейнера в одном приложении!

Теперь по порядку.

TransientContainerService

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

TransientContainerService наследник интерфейса, использующий Unity поумолчанию.


TransientDependencyStrategy

Для того, чтобы вся конструкция заработала необходимо добавить сию стратегию в коллекцию стратегий ObjectBuilder'а на этап инициаллизации объекта (т.к. объект к этому моменту уже должен существовать). Ее задача находить "временные" (transient) зависимости и вычислять их.

TransientDependencyAttribute

Для того, чтобы стратегия поняла что именно необходимо внедрить, зависимость необходимо пометить атрибутом TransientDependencyAttribute. У этого аттрибута есть строковое свойство Id, с помощью которого можно вычислить именованную зависимость.
Основная работа по выведению зависимости происходит в методе GetValue внутреннего класса TransientParameter. Зависимость выводится при помощи вышеописанного сервиса ITransientContainerService, простой делегацией этого процесса Unity. Далее, созданный объект пропускается через конвеер стратегий ObjectBuilder'а, с тем чтобы внедрить в него зависимости, т.к. он тоже может иметь зависимости в свойствах и методах. Единственное ограничение здесь будет необходимость в конструкторе указывать тоько временные зависимости, т.к. создавать этот объект будет не ObjectBuilder CAB'а, а Unity.


Пример.

Так может выглядеть это в коде:



Удачи!

P.S. Ниже привожу список ресурсов и статей, полезных для понимания ObjectBuilder, Unity и IoC вообще.
http://tavaresstudios.com/Blog/post/Deconstructing-ObjectBuilder---Introduction.aspx

http://tavaresstudios.com/Blog/post/Deconstructing-ObjectBuilder---What-Is-ObjectBuilder.aspx

http://tavaresstudios.com/Blog/post/Deconstructing-ObjectBuilder---Combining-Strategies.aspx

http://tavaresstudios.com/Blog/post/Deconstruction-ObjectBuilder---Wiring2c-part-1.aspx

http://tavaresstudios.com/Blog/post/End-of-the-Deconstruction.aspx

http://davidhayden.com/blog/dave/archive/2008/02/27/UnityConstructorInjectionGreediestMostParametersTheLineHasBlurredMaybe.aspx

http://weblogs.asp.net/podwysocki/archive/2008/03/25/ioc-and-unity-the-basics-and-interception.aspx

http://weblogs.asp.net/podwysocki/archive/2008/02/22/ioc-and-the-unity-application-block-going-deeper.aspx

http://weblogs.asp.net/podwysocki/archive/2008/02/26/ioc-and-the-unity-application-block-once-again.aspx

http://weblogs.asp.net/podwysocki/archive/2008/03/04/ioc-containers-unity-and-objectbuilder2-the-saga-continues.aspx

http://www.hanselman.com/blog/ListOfNETDependencyInjectionContainersIOC.aspx

четверг, 17 июля 2008 г.

AddIns in Plugins or much of muchness. Part 1. Overview.

Совершенно верно, вы не ошиблись, и это не двоит у вас в глазах. Именно маслом маслянным я реализовал подключение стронних плагинов в композитном клиенте, построенном на SCSF (Smart Client Software Factory).

Существует много терминов для обозначения одного и того же паттерна проектирования - подключаемого модуля (plugin, addin, addon и т.п.). Ребята из P'n'P назвали подключаемые модули в CAB -plugins, а парни из Managed AddIn Framework (MAF) - addins, соответственно. Сразу возникает вопрос - а зачем вообще мешать все в кучу, если в CAB и так уже plugin является краеугольным камнем? Ответ прост - в MAF заложено множество возможностей по работе с плагинами (так и буду дальше их называть), которых нет в CAB. Например: возможность изоляции плагинов в отдельных доменах или даже процессах, возможность запуска плагинов с различными уровнями безопасности и др.

Хороший пример работы с MAF написал Sacha Barber здесь. А вообще, очень много полезной информации можно почерпнуть в блоге комманды MAF.

Дизайн системы следующий: в Shell - оболочку программы, загружаются модули CAB, один из которых управляет подключением MAF плагинов. Схематично это можно представить себе
примерно так:


Модуль App.Addins публикует в настроечную инфраструктуру UI для управления состоянием MAF плагинов. Этот модуль управляет временем жизни и окружением MAF плагинов, позволяя вам подключать или отключать те или иные плагины, выбирать с каким уровнем изоляции и в какой домен/процесс загружать выбранный плагин.

Жизнь становится интереснее с плагинами, имеющими свой собственный UI, а также публикующими настройки! Для таких плагинов модуль App.Addin позволяет выбрать в какую часть UI приложения-хоста загрузить gadget (так я называю UI MAF плагина).

Возможным расположением gadget'ов заведует модуль App.Layout, загружающий layout из loose-xaml, который можно редактировать в любом xaml-дизайнере.


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

Part 2. MAF addin UI - gadget. Object model.

Part 3. WPF-interop and "airspace" notion - hosting gadgets in complex shape windows.
Part 4. Publishing MAF addin settings.
Part 5. Handling unhandled exceptions in isolated addin domains, gadget host.
Part 6. Cross-AppDomain events propagation powered by Juval Lowy's WCF Pub-Sub framework.

Удачи!

среда, 27 февраля 2008 г.

WPF command & CAB command - run into one another

В прошлый раз я писал про DM-V-VM "framework", как расширение к PnP smart client software factory (SCSF) и SCSFContrib. Одним из интересных моментов в snippet'e был класс ActionViewModel, в котором содержится список команд View, и который автоматически привязывает команды к UI. Там мы использовали класс CommandModel, являвшийся оберткой над WPF командой. Но CAB также предлагает совю реализацию паттерна Command. И как же быть? какую реализацию использовать?

CAB реализация имеет преимущества доступа к IoС-контейнеру, а следовательно и ко всем ресурсам (локальные сервисы, ресурсы, виды) composite приложения, разработанного при помощи CAB. А WPF команда тесно интегрирована с WPF framework'ом и следовательно вам не надо будет заботиться о логике опроса команды и управления состоянием UI. Как использовать преимущества одной и другой реализаций?

Здесь я собираюсь рассказать о своем решении, являющемся слиянием 2ух сущностей - WPF command и CAB command.
На самом деле в SCSFContrib (интеграция WPF и CAB) Kent Boogaart уже все сделал - WrappedCabCommand, но у его решения есть серьезный недостаток - невозможность передачи параметра команде. Эту проблему я решил, развив его идею.
IActionCatalogService

Основное усовершенствование WrappedCabCommand заключается в использовании IActionCatalogService - локального сервиса SCSF, как основного обработчика команды и ее состояния. Здесь есть хорошее описание данного локального сервиса SCSF. Сервис этот связывает действия(actions) с методами, помеченными атрибутом [Action(ActionName)],


регистрирует условия(conditions) - код реализующий логику принятия решения о возможности выполнения конкретного действия,

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


Методы, помеченные атрибутом [Action(ActionName)] должны быть публичными и соответствовать сигнатуре делегата ActionDelegate. Подпиской методов управляет ActionStrategy из SCSF, так что для присоединения метода к действию, надо либо создать класс, в котором этот метод определен, с помощью IoC-контейнера CAB, либо просто добавить его в коллекцию WorkItem.Items.


Условия - классы, реализующие интерфейс IActionCondition. Автоматически вызываются сервисом IActionCatalogService перед выполнением действия, либо явно вызываются клиентом через тот же сервис. Сервис строит из условий цепочку и конечный результат является произведением всех отдельных результатов опроса IActionCondition.


СAB Command.

В CAB команды представляются коллекцией объектов Command, хранящихся в WorkItem, доступ к которым осуществляется по уникальному строковому идентификатору. Сам CAB работает с командами через наследников класса CommandAdapter, управляя подписчиками, триггерами и т.п. Command'ы создаются CommandStrategy ObjectBuilder'a (OB), когда в процессе создания объекта OB встречает публичные методы-обработчики комманды, соответсвующие сигнатуре EventHandler'a и помеченные атрибутом [CommandHandler(CommandName)]. CommandAdapter содержит внутреннюю таблицу триггеров(объектов вызывающих выполнение команды) и подписчиков на указанные сообщения. И когда происходит событие в объекте-триггере, указанное при привязке триггера к команде, WorkItem вызывает всех обработчиков подписанных при помощи [CommandHandler(CommandName)].


Добавление UI-control'a в список триггеров команды ActivateOverview.

Метод-обработчик команды ActivateOverview. Будет автоматически добавлен в список методов-обработчиков CommandAdapter'a для комманды ActivateOverview.

WPF Command.

В WPF команды представляются объектами, реализующими интерфейс ICommand. В библиотеке есть 2 класса реализующих этот интерфейс - RoutedCommand, RoutedUICommand, которые маршрутизируют события CanExecute и Executed по всему визуальному дереву. Сами команды по умолчанию не содержат никакой логики, а предлагают реализовать ее любому объекту, подписавшемуся на эти комманды через CommandBinding где-либо в визуальном дереве. UI элементы, с которыми можно связать комманду, реализуют интерфейс ICommandSource и содержат свойства: Command - собственно комманда (объект с интерфейсом ICommand), CommandParameter - параметр, который передается команде (необязательное свойство) и CommandTarget - цель, на которой эта команда должна выполняться (необязательное свойство).

Come together!

За основу для объединения сущностей CAB и WPF комманд возьмем класс CommandModule.


Реализацией идеи является класс ModuleCommand:

Интерфейс класса ModuleCommand. IActionCatalogService инжектируется IoC-контейнером CAB при создании.

В отличие от WrappedCabCommand этот класс не требует передачи ему объекта CAB комманды. CAB комманду он находит сам, для этого ему лишь требуется имя - CommandName.

Для уникальной строковой константы CommandName принято соглашение - она идентифицирует следующие сущности: действия IActionCatalogService, Cab команды: собственно команду CommandName, и если определены строковые константы CommandName с префиксами Before[CommandName] и After[CommandName] - команды-тригеры до и после выполнения действия.


Переопределенный виртуальный метод OnQueryEnabled автоматически вызывается WPF framework. В нем производится опрос команды и ActionCondition'ов можно ли выполнять команду, вынося всю специфичную логику наружу (вызов CanExecute сервиса), и в зависимости от результата UI соответственно отображает состояние - enabled или disabled.

В интерфейсе класса есть свойство ActionTarget - дополнительный параметр, который команда будет автоматически передавать в метод-подписчик на действие.
CAB комманда учавствует в логике управления состояния комманды через свое свойство State. Оно влияет на принятие решения в OnQueryEnabled класса ModuleCommand.


Переопределенный виртуальный метод OnExecute вызывается WPF framework при перехвате события от UI элемента, связанонго с командой и вызывающего ее срабатывание, и делегирует выполнение кода триггеров и собственно команды IActionCatalogService и CAB командам.
Таким образом путем делегирования логики принятия решения о состоянии комманды и логики выполнения комманды IActionCatalogService решается проблема слияния CAB и WPF сущностей комманды. CommandBinding связывает комманду с UI, а IActionCatalogService связывает комманду с инфраструктурой CAB. Использование IActionCatalogService также позволило решить проблему передачи параметра в команду, т.к. CAB реализация команды такой возможности не предусматривает.

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

I said work!

Дабы не быть голословным и продемонстрировать решение в действии давайте модифицируем пример из прошлого поста. Основные изменения коснутся FooViewModel. Теперь коллекция его комманд будет состоять не из двух объектов различных классов с жестко зашитой в их код логикой, а из двух объектов одного типа (ModuleCommand), логика которых будет делегирована IActionCatalogService.


+ в проекте добавятся 2 класса:
обработчик действий


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


+ список идентификаторов:

Удачи!

PS: Интересный подход к реализации логики комманд демонстрирует Josh Smith в своей статье. Суть его предложения в следующем - сделать RoutedCommand "умнее", добавив ей самой возможность определять логику. Он наследуется от RoutedCommand и в классе наследнике вводит attached-свойство, помечающее UI-объект как обработчик комманды и вводящий 2 абстрактных метода OnCanExecuteCore и OnExecuteCore для реализации логики в классах наследниках. Таким образом можно CommandModel заменить на SmartRoutedCommand, но в таком случае, усложнится xaml UI, т.к. там надо будет прописывать CommandSink'и каждой команды вручную.
PPS: вспомнил, что видел еще одну реализацию основанную на использовании CAB EventBroker. Работает, и кстати в реализации проще, но считаю, что зря чувак смешал сущности.
UPD: выложил код для скачивания здесь.

четверг, 21 февраля 2008 г.

Model-View-ViewModel for WPF

В моем проекте весь UI построен на очень простом и одновременно очень мощном паттерне проектирования клиентских приложений DM-V-VM (DataModel-View-ViewModel), для реализации которого я написал простой framework (модное слово не удержался), с огромной помощью серии статей Dan'a Crevier(см. ссылку выше).
Основной технологией для разработки клиентских приложений в проекте был выбран WPF, в комбинации с Composite Application Block (CAB). В этом framework'е (CAB) активно используются и стимулируется разработчиком использование 2 паттернов для клиентских приложений: Model-View-Controller (M-V-C) и Model-View-Presenter (M-V-P). Мой же "Framework" является расширением и дополнением к CAB и SmartClient Software Factory (SCSF) - фабрики построенной на Composite Application Block), и использует часть ее функциональности, а в частности IoC-контейнер CAB для выведения зависимостей классов.

Паттерн DM-V-VM является частным случаем другого более общего паттерна написания UI кода - M-V-P, и в терминах Фаулера может называться PresentationModel. Есть с ним одна проблема - он шикарно вписывается в модель программирования с использованием WPF, но не позволит с легкостью переключиться на другую UI технологию, как это позволяет M-V-P (можно почитать у Dr.WPF). И, если вы готовы с этим мириться (а я очень даже готов, т.к. не планирую менять UI технологию), то эта модель в самый раз. Да, и, возможно, в сложных UI (например отображение и редактирование данных из многих, > 5 источников данных) поддержка ее также окажется черезчур сложной. Здесь я бы посоветовал использовать M-V-P, но на практике с такими случаями я пока не встречался.
Начну же препарировать данный паттерн.
DataModel(DM)

Классы, которые поставляют данные (бизнесс объекты). Их обязанность поставлять бизнес-объекты для отображения и редактирования в UI в WPF-дружественной манере (например, реализуя INotifyPropertyChanged). Все обращения к свойствам этих классов должны производиться в UI потоке и ни в коем случае не должны блокировать его.

На мой взгляд, DM отлично вписывается в SOA архитектуру, поставляя данные для Presentation слоя из SOA инфраструктуры, являясь промежуточным шлюзом данных.

На самом деле данные DM может получать из самых различных источников, что реализуется через делегирование общения с поставщиком данных Provider'у (можно было бы назвать и стратегией). Многое в реализации базового класса взято из DM Dan'a Crveier, поэтому не буду приводить здесь весь код, а сосредоточусь на усовершенствованиях его класса.

  1. Первое усовершенствование - введение базового интерфейса для DM и введение generic-интерфейса для наследников, параметризуемых Provider'ом.

  2. Введение типизированного провайдера и создание generic интерфейса DM с ограничением на поставляемый тип данных.


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

Введение generic интерфейса для Provider'а позволило написать generic класс DM параметризуемый интерфейсом поставщика и типом данных SEModel (SingleEntityModel SEDM), избавив тем самым от написания многих классов наследников, занимающихся только тем, что поставляют разные типы данных.



Обратите внимание, что в классе у свойства Entity присутствуют публичные get и set методы, а в интерфейсе только get. Это сделано для того, чтобы обеспечить возможность асинхронной установки значения свойства по его имени через PropertyDescriptor, инкапсуляция же обеспечивается через публикацию только лишь интерфейса ISEDM, где Entity - свойство только для чтения, т.е. клиенты взаимодействуют только через интерфейс, который может "инжектироваться" как зависимость в клиенты IoC-контейнером.
View

VM про класс View ничего не должен знать, и общается с ним через интерфейс IView. Как я уже упоминал выше, DM-V-VM не позволяет с легкостью менять UI технологию, не переписывая кода VM (так как в VM вынесены такие technology-specific понятия как WPF command), то я просто для упрощения дальнейшего кода ввел интерфейс IWPFView, в котором опубликовал специфичные для WPF свойства. Такое вот волевое решение. =)


ViewModel

И, наконец, главный участник троицы - ViewModel. Этот класс должен содержать всю логику UI. VM инстанцируется при создании View IoC-контейнером и выводит все его зависимости. Базовый класс ViewModelBase параметризуется интерфейсом View, а его наследник в дополнение еще параметризуется интерфейсом DM.


Эти классы вводят ряд полезных методов для перегрузки, вызываемых при установке/удалении вида/модели, при перехвате событий от модели о успешном обновлении/ об ошибке. Также VM при получении View подписывается на его события Loaded/Unloaded и предлагает виртуальные методы для перегрузки вызываемые при перехвате этих событий. В реализации по умолчанию VM в методе OnViewSet присваивает IView.DataContext указатель на себя, с тем, чтобы IView мог обновляться самостоятельно при перехвате событий от VM, DM и бизнес-объектов, поставленных DM. Таким образом в терминах Фаулера View является ActiveView.

На диаграмме классов также можно заметить класс ActionsViewModel. Этот класс содержит список CommandModels (также очень удобная конструкция от Dan'a Crevier инкапсулирующая WPF комманды), на каждую из комманд которого ActionsViewModel автоматически создает в View CommandBinding.

ViewModel в данном его виде не запрещает редактировать данные, поставленные DM и никаким образом не следит за валидностью и состоянием объекта (в процессе редактирования он просто может быть приведен в невалидное состояние, что затем вызовет цепочку ошибок). Во избежание этого в моем проекте все объекты редактируются в Wizard'ах, которые производят валидацию этих объектов и не дают сохранять объекты в промежуточном или невалидном состоянии (об этом в следующем посте). Если же есть необходимость редактировать бизнес-объект прямо в View, то рекомендую прочитать вот этот пост от Pete W.

DM-V-VM

Соберем теперь пример. Пусть у нас будет некий объект Foo, который будет поставляться SEModel (вот где пригодилась функциональность generic класса - не надо писать новый класс!) при помощи FooProvider.





Вся логика содержится в FooViewModel. Этот класс наследуется от ActionsViewModel и содержит 2 CommandModel: IncCommandModel и DecCommandModel. IncCommandModel увеличивает значение Foo.Bar, а DecCommandModel уменьшает. При загрузке View ViewModel просит DM обновиться.




Теперь очередь View.

В code-behind при создании View создается ViewModel.



И, поскольку ViewModel уже установил View.DataContext на себя, то можно в View поместить ContentControl, "забайндить" его свойство Content на DataContext (он используется поумолчанию, если в Binding не указан путь) и написать темплейт для рендеринга ViewModel.



И здесь мы столкнемся с единственным злом generic класса DM - binding далеко не всегда может найти у этого класса указанный путь, поэтому здесь мы явно при перехвате успешного обновления DM в FooViewModel сохраняем объект Foo в ресурсах View.



Да, если не использовать IoC-контейнер, то все [CreateNew] и [ServiceDependency] надо заменить на вызов конструкторов.

Вот, что у нас получилось:


Удачи.
PS: В ближайшем будущем выходит новый проект от PnP group - Prism. Он призван послужить заменой CAB в процессе разработки LOB приложений на WPF. Жду.
Upd: выложил код примера здесь.