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

четверг, 11 августа 2011 г.

Execute Caliburn coroutine in action pipeline without UI trigger

Seems rather easy, isn't it?

Just put it into Presenter's Execute method and the did is done. Well, should it be like that I wouldn't wake up in the night and write this stuff!
Let's see what's there in Execute:

So, it just packs every single coroutine it has in SequentialResult and execute 'em. No filters pipeline used, no routedMessage. None of your neat filters applied to these coroutines.

You might ask what is the difference with executing coroutines as a result of some UI action, be it a routed event or a command. The answer is in the Caliburn.PresentationFramework.Actions namespace.
To be prrecise it is an Action Execute overloaded method that in simple case of SynchronousAction does the following:

namely calls preprocessor filters, delegates a call to method that might be returning coroutine, handles errors with rescue filters, calls postprocessor filters. That is a filter pipeline I am talking about.
Well, seems that Caliburn has everything I need. And all I have to do is just use it.

As header states there is no UI trigger, i.e. I want to execute a coroutine when nobody clicked a button, but rather on some external trigger. As an example, consider model update with call to any data storage during Presenter initialization stage.

Enter DegenerateMessage. This intellectual piece of code is a data container that is being handed to participants on courutine call pipeline in order to hint what was the original method name called.

Another intellectual piece of code is DegenerateMessageTrigger. It is so damn smart that it knows that in order to trigger an action it has to pass a message to message handler!

And yet another piece is a DegenerateMessageHandler. It really shouldn't be called that way as it performs really complicated things. Namely, it creates an ActionHost and when Process is called converts DegenerateMessage to an ActionMessage and put it into the standard Caliburn action pipeline.

If it seems like dancing on your head, then know that you are not alone. But wait, there is another part I forgot to mention. It is a simple interface IResultExecutor aimed to hide all that smart code under two methods accepting Expressions with lambdas calling methods that return coroutines.

Oh, and there is yet another thing. With Caliburn Micro you can accomplish the same with only one line of code.

Cheers!

четверг, 24 июня 2010 г.

Trigger Caliburn action status update through Prism's IEventAggregator

И Caliburn и Prism, в принципе решают одни и те же задачи - создание композитных приложений на WPF. Но, вот Роб Эйзенберг (создатель Caliburn) не считает, что они друг друга взаимоисключают, а я и подавно с ним согласен.
Я использую следующие "фичи" Prism'a:

  • modularity;
  • region manager;
  • event aggregator;
, а из Caliburn следующие:
  • actions engine;
  • application & model framework, даже не смотря на то, что с первого взгляда model framework кажется "overarchitected";
  • convention over configuration;
Вот сейчас я вам расскажу как расширить actions в Caliburn, при помощи Event Aggregator из Prism.
Главный способ расширения - это написание фильтров, которые вставляются в последовательность выполнения action. С их помощью можно проверять предусловия или состояние данных (к примеру можно проверить обладает ли Principal достаточными правами для выполнения действия), дабы определить статус action и обновить эффекты, применяющиеся к UI элементам. Примером такого фильтра является поставляющийся с framework фильтр Dependencies, который вызывает исполнение всей последовательности фильтров, на основании изменения какого-либо свойства, имя которого передается в качестве параметра ему. Но что, если вам нужно просто обновлять статус action, не связанный ни с каким полем в объекте, на основании какого-либо события/условия. Вы можете завести специальное поле и передергивать его значение или даже еще лучше - не заводите никакого поля, а просто кидайте нотификацию с именем несуществующего поля, переданного фильтру. Не знаю как вам, а у меня от таких решений зудит в одном месте.

Так вот зуд мой унялся только когда я написал фильтр, запускающий всю цепочку обработки статуса события по получению уведомления от EventAggregator сервиса Prism.

DependsOnEvent




Фильтр применяется как атрибут, которому передается тип события. Тут есть одно ограничение - событие должно быть унаследовано от generic типа CompositePresentationEvent закрытого типом object, т.е. CompositePresentationEvent_Of_Object. В методе Initialize происходит магия с помощью reflection. Дело в том, что IEventAggregator Prism имеет исключительно generic интерфейс, за что можно разработчиков его только поругать. Мы получаем событие переданного типа и
подписываемся на него. Вот именно для этого и приходится колдовать с волшебным зеркальцем, т.к. заранее тип события не известен. Часть, относящуюся к запуску обработки action, я содрал из кода упомянутого выше Dependencies фильтра.


Здесь мы создаем Observer, который будет следить за изменениями зависимых свойств. Т.к. реально никакие свойства не меняются я просто использую трюк с несуществующим свойством - в Initialize при подписке на событие я передаю делегат, который кидает событие обновления свойства Update, которого-то как раз и нет. Observer, поймав это событие запускает всю цепочку обновления статуса action.




PublishEventAfter
Ну и напоследок, фильтр, выполняющий все ровно наоборот, после выполнения action он кидает событие, которое обрабатывает EventAggregator.




Удачи!

вторник, 27 января 2009 г.

Much of muchness. Part 6. XAppDomain EventBroker.

Алилуйя! Мы добрались до заключительной (и самой интересной) части.
Здесь я расскажу, свой подход к реализации кросс-доменного EventAgregator'а.

Необходимость создания этого монстра диктует плагинная архитектура - различные изолированные модули, ничего не знающие об остальных плагинах, должны уметь оповещать окружающих о каком-то важном событии, причем получатели сообщения, обязательно будут жить в другом, изолированном домене.
Фундаментом моей платформы является CAB - composite application block, содержащим реализацию EventAggregator'а. Как сделать так, чтобы он не был ограничен одним доменом? Никак. Дело в том, что в CAB композиты публикуют события и подписываются на них при помощи ObjectBuilder pipeline, в котором специальная стратегия EventAggregator'а аккумулирует всю эту информацию. В нашем же случае все объекты плагинов будут создаваться MAF pipeline'ом, и доступа к ним у нас не будет. Так как же быть? Использовать WCF - Windows Communication Foundation.

В WCF из коробки нет реализации механизма Publish-Subscribe, но есть все средства для его реализации. Подходов может быть несколько:

За основу решения я взял последний framework от Juval Lowy и расширил его, сделав его contract&topic based, с тем, чтобы интегрировать его с CAB EventAggregator (topic based). Но обо всем по порядку.

Publish-Subscribe framework (С) Juval Lowy IDesign.

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

Прежде всего небольшим изменениям подвергся контракт сервиса подписки. Теперь ему передается не имя метода, которое надо вызвать у подписчика, а некий объект, выражающий интерес в подписке, который содержит имя метода (contract based) и имя topic (topic based), по которым EventAggregator сможет определить заинтересованность того или иного подписчика в неком событии.





Интерфейс сервиса подписки кросс-доменного EventAggregator'а для моей платформы выглядит следующим образом:

основное его назначение - сообщать WCF инфраструктуре о том, что IEventNotifyCallback - это callback contract для нашего кросс-доменного EventAggregator'а. DomainDTO - это суперкласс для моих DTO.

Сам сервис реализован в классе XAppDomainEventBrokerSubscription.

Он выполняет функции сервиса подписки и связывает инфраструктуру CAB с нашим механизмом. Последнее осуществляется аналогичным CAB'овскому образом - поиск публикаторов событий (по соответствующим аттрибутам) и аккумуляция этих публикаторов в CAB EventAggregator с одной лишь разницой, что вместо подписчика подставляется суррогатный объект, который при получении события опубликует его в нашей кросс-доменной системе (фактически просто пробросит дальше). Интегрируется этот сервис в CAB при помощи специальной стратегии ObjectBuilder'а XAppDomainEventBrokerStrategy.


Публикацией ведает XAppDomainEventBrokerPublication - наследник слегка измененного PublicationService сервиса из framework'а Juval Lowy. Первый реализует callback контракт как контракт сервиса, а последний на основании вызываемого метода и topic находит и вызывает соответствующий метод у соответствующего подписчика.


Но возникает резонный вопрос - а где, собственно, этому сервису передается topic? И куда его вообще воткнуть, если это contract-based framework? Ответ прост. Засунуть в заголовок
сообщения и занимаются этим клиенты сервиса. Их только надо унаследовать от PublisherServiceClient - часть моей инфраструктуры WCF (может быть опишу попозже).


Обратите внимание - у базового клиента сервиса подписки есть свойство EventTopic, с помощью которого мы можем передать это значение в WCF pipeline через расширение канала EventTopicChannelExtension, с одним единственным свойством - EventTopic.
Помните суррогатные объекты, добавлявшиеся в EventBroker CAB? Так вот они публикуют события через EventPublisherClient, передавая ему значение EventTopic из аттрибута.
Как EventTopicChannelExtension попал в канал? Все тот же базовый клиент при создании фабрики каналов добавляет EventTopicFlowBehavior, выполняющий всю работу.




Собственно всю работу делает даже не он, а следующие два джентельмена:



Первый, как следует из его названия, инициаллизирует канал, добавляя в него уже упоминавшееся выше расширение EventTopicChannelExtension для передачи EventTopic второму джентельмену, который собственно добавляет этот topic в заголовок передаваемого сообщения.
Из этого самого заголовка, слегка модифицированный мной, PublicationService вычленяет EventTopic и фильтрует по нему подписчиков.

Вот и все! Теперь все, что нужно сделать по шагам:
1. Создать прокси к сервису подписки и подписаться на интересуемое событие, указав при желании метод контракта, который следует вызвать.
2. Создать прокси к сервису публикации и вызвать, мимикрирующий под контракт подписчика сервис публикации, передав при желании topic.

Не очень надеюсь вы не запутались, потому что сам запутывался не раз.

P.S. ссылки на предыдущие посты в этой серии
Удачи!

пятница, 5 декабря 2008 г.

Much of muchness. Part 5. Handling unhandled exceptions in isolated addin domains, gadget host.


Итак, мы приближаемся к концу серии! Что же нас ждет в этой части?

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

Sabotage! Разрабатывая приложение, открытое для расширения при помощи плагинов, вы можете столкнуться с ситуацией, в которой плагины, разработанные сторонними и не очень добросовестными производителями, будут рушить ваше приложение. Вы спросите зачем нам что-то делать, если они и так уже изолированы в sandbox. Но сложность заключается в том, что если в одном из доменов плагинов произойдет необработанное исключение, то кверху лапками полетит все приложение. Решение мое, конечно же основано на рекоммендациях комманды MAF, о том что делать в таких случаях: И состоит это решение из 2ух частей:
  • UnhandledExceptionHelper - вспомогательный класс для обработки исключительных ситуаций, как следует из его названия;
  • GadgetHost - хост-обертка для гаджетов, машина состояний, изменяющая своё представление в зависимости от состояния плагина/гаджета;
UnhandledExceptionHelper
Интерфейс этого незаменимого помощника выглядит следующим образом:



Следуя названию, его призвание обрабатывать исключительные ситуации в плагинах и не давать при этом основному приложению умереть. Как он это делает? Он обманывает CLR, так что его скорее надо было бы назвать UnhandledExceptionLiar =). Обман заключается в перехвате события необработанного исключения из потока в другом домене и блокировка этого потока до завершения приложения. Это единственный способ, который я нашел для безопасного перехвата исключения в домене плагина без последующего закрытия всего приложения. Следует заметить, что если исключение произойдет в UI потоке, даже не смотря на то, что код выполняется в другом домене (ибо потоки умеют пересекать границы доменов), то приложение рухнет. Это может произойти, например, при настройке параметров плагина или в логике gadget'а. Блокировка умирающего потока происходит при помощи метода Lock(), в котором другой поток (как правило UI), захватывает эксклюзивную блокировку. Кто же вызывает этот метод? Неужели это должен делать разработчик? Нет. Об этом и о многом другом позаботится GadgetHost.

GadgetHost.

GadgetHost это контрол, с помощью которого можно управлять состоянием нашего Gadget'a. Он принимает на вход AddInToken, инициаллизирует Gadet в background потоке, а затем, по вызове функции ShowGadget, как следует из названия, создает гаджет и размещает его как свой контент в UI.



При получении AddInToken GadgetHost создает экземпляр UnhandledExceptionHelper'а, который оповещает последнего о поломке и не дает приложению упасть. При поломке надо срочно привлечь внимание пользователя! И с этой функцией справляется мой FaultedGadgetPlaceholder - просто UserControl с эффектной картинкой (полчаса в Paint.Net =) ).

Вкратце так. Не пишите падающие гаджеты! =)

Удачи!

среда, 12 ноября 2008 г.

Much of muchness. Part 4. Publishing MAF addin settings.

Различные виды кризисов задержали продолжение этой серии. Считаю, что надо ускориться и покончить с ней.

Сегодня опишу механизм публикации настроек плагинов. Построен он на инфраструктуре, которую я описывал ранее. Рекомендую для начала ознакомиться с ней, для того чтобы понимать о чем будет идти речь. Вкратце, решение состоит из нескольких интерфейсов, определяющих функциональность, пары классов для хранения настроек (примитивных типов данных, строк и т.п.) и конфигурационного диалога, который управляет отображением всего этого добра. Для плагинов потребовалась небольшая адаптация в виду того, что загружаются они в изолированые sandbox'ы.

Итак, может так статься, что вам понадобится управлять какими-то параметрами плагина. Есть несколько способов это сделать: поместить в UI плагина какой-то элемент
, например кнопку, которая будет запускать форму настроек; а можно делать это централизовано, в единой форме настроек хоста, что лично мне кажется более правильным.

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



Здесь IAddInTuned - интерфейс, который должны реализовывать классы настроек плагинов. Его дополнительная функция, помимо управления настройками, это передача через границу домена несереализуемого объекта изображения WPF, что реализуется в базовом классе AddInTuned при помощи кодирования изображения в массив байтов и последующего восстановления на стороне хоста.



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



Передается этот объект настроек и пользовательское представление, как вы наверное помните представлению объекта хоста на стороне плагина HostObject.



который затем по цепочке адаптеров передает эти объекты и в адаптере стороны хоста создает обертку AddInTunedHostWrapper для прозрачного представления настроек хосту.



AddInTunedHostWrapper делегирует все входящие вызовы внутреннему переданному TransparentProxy, который инициирует вызов методов объекта AddInTuned в домене плагина через remoting.

Вот так вкратце выглядит решение для передачи настроек плагинов на сторону хоста.

Ну и приведу пример кода, дабы было понятнее.
Сторона плагина.
Сам плагин.

Настройки плагина.


Представление настроек.

Результат.


Надеюсь я вас не разочаровал убогим примером.


Удачи!

среда, 24 сентября 2008 г.

Much of muchness. Part 3. WPF-interop and "airspace" notion - hosting gadgets in complex shape windows.

Эта часть будет короткой, и по большей части к разработке самих плагинов не относится. Она, скорее, о WPF-interop, и о том какие проблемы возникают при хостинге gadget'ов (UI плагина) в WPF.

Как я уже говорил в предыдущей части, проблема возникает вследствие того, что представление плагина рендерится не WPF движком (построеном на DirectX), а средствами Win32. Вообще в WPF можно хостить контент отрисованный другими технологиями, но при этом возникает целый ряд проблем, которые в Microsoft очень метко обозвали "airspace". Суть этой проблемы заключается в том, что при хостинге UI отрисованного различными технологиями, эти части UI находятся в изолированных пространствах и подчиняются правилам тех технологий, которые занимаются рендерингом. В нашем случае проблема возникает в случае использования Layered Windows. При попытке захостить gadget в окне с установленным флагом AllowsTranspency и Transparent Background, эта проблема встает в полный рост - WPF владеет всеми пикселами окна и любой другой не-WPF контент будет просто невидим.

Вы спросите зачем нужны layered windows? В WPF это наиболее простой способ создания окон сложной формы - окон без стандартного заголовка (chrome). Но выход есть, и если вы хотите хостить UI плагина в окошках со сложной формой, вам необходимо использовать альтернативный способ создания окна без стандартного заголовка, а именно WinAPI Regions.

Далее привожу код для окна с нестандартным chrome.

Здесь в OnSourceInitialized мы захватываем обект HwndSource, который является (согласно его названию) поставщиком HWND для окна WPF и расширяем стиль окна.

Поскольку наше окно будет лишено стандартного заголовка, то надо как-то его таскать. Об этом заботится обработчик OnMouseLeftButtonDown.

WPF and Win32 Interoperation Overview

  • Part 1. Overview.
  • 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.

Удачи!

среда, 3 сентября 2008 г.

AddIns in Plugins or much of muchness. Part 2 MAF addin UI - gadget, Object model.

//тут введение. можно пошутить, но лучше не стоит.

Пора описать реализацию.

Object model.

Для того, чтобы System.AddIn framework мог обнаружить и хост мог подключить плагины, мы должны должны реализовать требуемый framework'ом pipeline, состоящий и 5 компонент:

  • контракта плагина и, если необходимо, хоста;
  • представления хоста (то как хост видит плагин - изолирует хост и от контракта и от самого плагина подавно);
  • представления плагина (то как плагин видит хост - -//-);
  • адаптеры стороны хоста, транслирующие вызовы представления хоста в вызовы контракта - наследники представления хоста; адаптеры, транслирующие вызовы контракта в вызовы представления хоста - наследники контракта;
  • адаптеры стороны плагина, транслирующие вызовы контракта в вызовы представления плагина - наследники контракта; адаптеры, транслирующие вызовы представления плагина в вызовы контракта - наследники представления хоста.
Теперь по пунктам. Плагины предназначены для публикации в хост гаджетов, соответственно и называются они XXXGadgetAddIn.

Contracts.




IGadgetAddInContract - контракт, с помощью которого хост общается с плагином (функциональность, которую хост требует от плагинов).
IHostContract - контракт, с помощью которого плагин может общаться с хостом (функциональность, которую хост предоставляет плагину).
IAddInTunedContract - вспомогательный контракт для передачи настроек плагина хосту (подробнее о нем я напишу в одной из следующих частей).
IServiceLocatorContract - обертка для передачи набора сервис-локаторов плагину.

Метод Initialize, согласно его имени, предназначен для инициаллизации плагина. В качестве параметра ему передается контракт объекта хоста. Возвращает же этот метод некий id, позволяющий однозначно идентифицировать плагин.



метод Initialize TestGadgetAddIn.

InitializeServices предназначен для инициализации сервисной инфраструктуры плагина, загруженного в другой домен или процесс (я использую ServiceLocator как форму IoC).



метод InitializeServices TestGadgetAddIn.


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

Последний метод контракта обязательно должен вызываться из основного потока приложения (UI-поток), т.к. он возвращает представление (gadget) - UI контрол, который будет добавлен в визуальное дерево представления хоста, что само собой влечет за собой требование идентичности Dispatcher'ов контролов.




HostViews.



Представление хоста, как я уже писал изолирует хост от контракта, позволяя изменять версии контракта и соответственно плагина безболезненно для хоста (т.е. он сможет работать даже с более новыми плагинами, реализующими другой, может быть более полный контракт), без перекомпиляции. Для этого лишь понадобится написать новый адаптер. Абстрактный класс представления плагина хостом наследует и реализует адаптер хоста. INativeHandleContract - это обертка, с помощью которой команде WPF удалость передать визуальный объект через границы домена. Реально в визуальном дереве хоста появится не ожидаемый контрол (gadget), который создает плагин, а специальный контрол AddInHost - наследник HwndHost, т.е. контрол позволяющий хостить внутри себя не-WPF контент. Отсюда вывод, что наш gadget будет рендериться не WPF-engine, а Win32. О проблемах, которые это за собой влечет и решении я напишу в одной из следующих частей.

HostAdapters.

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



Адаптер контракта плагина к представлению хоста.



Адаптер хоста к контракту.

Интересный момент здесь, то как передаются настройки - полученный контракт заворачивается в AddInTunedProxy, который восстанавливает UI-специфичные свойства из их сериализованного представления (свойство Image) и публикует настроечный UI при помощи встроенного класса FrameworkElementAdapters, определенного в System.Windows.Presentation.



Вызовы всех метод эта обертка делегирует внутреннему ITunedContract, которые выполняются в домене плагина.

AddInViews.

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




AddInAdapters.



Адаптер плагина к его контракту.
Просто делегирует вызовы контракту.




Адаптер контракта хоста к его представлению.
Наследует абстрактному классу представления хоста плагином. Настройки передаются в виде контракта AddInTunedToContractAdapter, который публикует proxy Tuned для AddInTunedProxy и UI.




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

Удачи!

четверг, 28 августа 2008 г.

Dependency Injection in xaml

Здесь я опишу свой подход к DI в Xaml. Особенность решения состоит в том, что я использую CAB как платформу для построения моих smartclient приложений, но, надеюсь, за реализацией вы увидите идею, которая позволит вам абстрагироваться от конкретного IoC framework'а.

Итак, довольно часто я создаю объекты в непосредственно в Xaml, и мне бы хотелось при этом не нарушать основные принципы, с которыми я разрабатываю приложение, например, использовать, также как и в коде, IoC-контейнеры для уменьшения связанности системы.



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

  • возможность использовать в Binding;
  • возможность применять к POCO объектам, создаваемым в Xaml.
Решение состоит из двух частей:
  • IoCProvider - custom DataSourceProvider (можно использовать в Binding);
  • набор MarkupExtensions для использования с POCO;
IoCProvider

Этот провайдер - ядро решения. Он занимается выведением зависимостей используя стандартный для CAB IoC контейнер - WorkItem.



Поскольку созданием этого объекта будет управлять WPF engine, то и "впрыснуть" в него зависимости не удастся, но можно использовать доступный отовсюду реестр. В случае WPF это объект Application, а поскольку мой Application обязательно реализует интерфейс IRootApplication, единственное назначение которого публиковать корневой WorkItem, то задача получить IoC контейнер не составляет более труда (см. конструктор IoCProvider).

Основную работу по выведению зависимостей Provider вы полняет в методе BeginQuery, вызываемом WPF binding engine. Инициировать запрос можно также и вручную просто дернув метод Refresh() базового класса DataSourceProvider.



Теперь этот провайдер можно использовать в Binding. Но при попытке привязать поле POCO объекта при помощи Binding к выведенной зависимости, вы получите InvalidOperationException, сообщающее вам о том, что Binding можно использовать только с Dependency Property. И точно, а как же быть?

Markup Extension.

Вот выход - использовать markup extension, ведь его можно использовать практически везде в xaml. Как пример, приведу расширение для "впрыскивания" зависимых сервисов WorkItem - ServiceDependencyMarkupExtension.



И вот результат - мы создаем объект и "впрыскиваем" его зависимости прямо в Xaml.


Надеюсь, теперь вы видите, с какой легкостью можно реализовать Dependency Injection в Xaml, и абстрагировать framework от реализации IoC контейнера. Можно, например, заменить WorkItem на IContainerFacade, как это сделано в Prism.

Вот несколько статей, которые помогут вам в реализации:
creating-a-custom-datasourceprovider
Injecting Xaml with Unity Application Block using Markup Extensions

Удачи!