четверг, 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

Удачи!

пятница, 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

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

Kodama - a spirit of tree.

Те из вас, кто читает мою англоязычную версию блога, возможно, читали мою заметку про то как ViewModel спасает людей (вообще говоря ViewModel капризничает и спасает только WPF developer'ов ). Мне было недосуг переводить ее, но вот вкратце ее содержание: хотите облегчить себе жизнь - используйте ViewModel где только возможно. В данном случае я использовал ViewModel при работе с ComboBox. Что из этого вышло? Вышел замечательный и удобный класс SelectorCollectionPresenter, жить с которым намного легче. =) В конце я в качестве бонуса опубликую его код. А сейчас я хочу написать не об этом.

Хочу написать я о ViewModel, взаимодействующем с TreeView. Вдохновение меня посетило когда я прочитал вот эту статью Josh'а Smith'а. C тех пор прошло уже много времени и я почти не работал с WPF непосредственно, а больше решал инфраструктурные проблемы (я начал серию заметок про масло масляное), но идея мне понравилась и засела в голове.

И вот, наконец, я обобщил и собрал воедино свое видение этой проблемы. В результате был написан контрол имитирующий TreeView "с душой". Назвал я его KodamaView. Kodama в японской мифологии - это дух живущий в дереве. А Kodama моего дерева, как вы наверное уже догадались это ViewModel.

- он сказал ViewModel?

Хочу сразу предупредить, что я просто собрал воедино в повторно используемый компонент свои наработки и идеи Josh'а.

KodamaView

KodamaView - это UserControl, который хостит внутри себя TreeView. Дабы сымитировать поведение TreeView он реализует интерфейс ITreeView, унаследованный от моего
базового интерфейса IWPFView путем делегирования всех вызовов внутреннему TreeView.


интерфейс для имитации TreeView.


Собственно сам контрол.


Стили и темплейты.

Kodama.

И теперь о самом главном - о душе.


Вот она душа дерева на картине
Toriyama Sekien

Моя кодама (если можно так выразиться) - это наследник класса ViewModel, параметризованного интерфейсами ITreeView и IDataModelBase. Последний объявлен базовым для того, чтобы ему подсунуть любую DataModel. Обновив ее, наследник Kodama получит список или иерархию объектов, которые отрендерит TreeView.

Для каждого TreeViewItem'а создается своя Kodama - TreeViewItemViewModel.



В данном классе предусмотрена возможность "ленивой" загрузки дочерних элементов при раскрытии узла (метод LoadChildren должен быть переопределен в наследниках) (честно сперто у Josh'а).



И наконец, я написал generic-класс TreeViewItemViewModel`1, единственное назначение которого - публиковать сущность для привязки в шаблоне.



В качестве примера - Kodama дерева департаментов.




А вот и бонус - SelectorCollectionPresenter. За всеми подробностями сюда.



Удачи!