четверг, 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. За всеми подробностями сюда.



Удачи!

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

Удачи!

вторник, 24 июня 2008 г.

WPF Presentation layer

Я не буду блоггерствовать следующие 3 недели, но чтобы вы не расстраивались вот вам мой Presentation Layer.

В этом проекте реализован небольшой framework для разработки десктопных приложений на WPF с примерами, которые я уже выкладывал в предыдущих постах. В основном это реализация шаблона DM-V-VM.

В качестве бонуса я включил исходные коды Wizard контрола.

Наслаждайтесь.

среда, 11 июня 2008 г.

Enterprise logging system. Part 5. Features.

Как я уже говорил, в LogBook предусмотрены дополнительные фичи, позволяющие использовать его в самых различных сценариях. Например:

Сцена 1. Сервис. Действующие лица: WCF, LogBook.

Поскольку framework WCF черезвычайно легко расширяется, подключить LogBook к нему одно удовольствие. На тему расширения WCF очень хорошо написал Aaron Skonnard здесь. Так вот, LogBook расширяет WCF при помощи поведения сервиса, реализованного в виде аттрибута, который вы можете декларативно применить к любому WCF сервису. Что же добавляет это поведение? Оно добавляет ErrorHandler ко всем ChannelDispatcher'ам и MessageInspector ко всем DispatchRuntime'ам EndpointDispatcher'ов.

Таким образом соотвественно настроенный LogBook может логировать ошибки и трассировать сообщения, происходящие при работе сервиса.


ErrorHandler, вызывает метод WriteErrorMessage фасада LogBook.


MessageInspector, вызывает метод WriteTraceMessage фасада LogBook.

Сцена 2. DLinq. Действующие лица: DLinq, LogBook.
В DLinq предусмотрено подключение простого логгера, в который будут сваливаться все запросы, выполняемые системой. Этот логгер должен быть наследником TextWriter и переопределять как минимум метод Write(string). Для LogBook я написал простенькую реализацию, которая будучи подключенной к любому DLinq DataContext'у будет трассировать все запросы.


Linq tracer.

Сцена 3. .Net. действующие лица: System.Diagnostics, LogBook.
И последнее. Вы можете подключить LogBook к системе трассировки .Net при помощи простого TraceListner. Довольно удобно для приложений, код которых вам недоступен. С помощью этого прослушивателя трейсов вы сможете перенаправить сообщения в сервис логирования.



TraceListener.

Кроме того, в System.Diagnostics.Trace нельзя было передать контекст. А здесь можно.

Вот вкратце все сладости в LogBook. Жду предложений по дополнительным фичам!

Удачи!

вторник, 10 июня 2008 г.

Enterprise logging system. Part 4. Facade.

Ура, я проснулся от летаргического сна.

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

Итак вперед.

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

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

Как вы помните в системе предусмотрено 5 уровней логирования:

  • Trace;
  • Info;
  • Warn;
  • Error;
  • Status;
, соответственно и интерфейс фасада определен количеством и именами этих уровней.

Что же скрывают за собой эти методы? Давайте рассмотрим работу LogBook по шагам.

При первом обращении к фасаду, прежде чем выполнить метод, clr будет вызван инициализатор типа. В этом статическом конструкторе LogBook пытается сконифигурировать себя. Этот процесс он делегирует статическому классу-загрузчику Bootstrapper, который сначала пытается получить настройки для текущего клиента от сервиса, а затем производит инициаллизацию хозяина (LogBook) из локального конфига (если вдруг в момент старта сервис будет недоступен, то он сможет поднять LogBook по локальным настройкам, которые сохраняются в конфигурационном файле клиента, а если не сможет, то кинет исключение).

Далее, в зависимости от уровня логирования LogBook создаст соответствующее сообщение и отправит его на валидацию в список подключенных фильтров (важно сделать это до окончательного заполнения сообщения, чтобы не тратить память и время зря, если оно не проходит по условиям фильтров). После этого, дабы не блокировать клиента, фасад LogBook делегирует отправку сообщения RequestManager'у, который в асинхронном режиме постарается отправить его куда следует. Куда следует - это сюда: первым делом он сохранит его в локальной стратегии, а затем попытается отправить его сервису. В случае неудачной попытки, он увеличит счетчик неудачных попыток, проверит условия устаревания сообщения (по времени хранения и количеству неудачных попыток) и сохранит обратно сообщение в локальную очередь, если оно все еще валидно. За подключением сервиса следит ConnectionMonitor, который при изменении статуса подключения генерирует событие, перехватываемое RequestManager'ом.

Все методы перегружены одним дополнительным параметром - контекстом, о котором я писал здесь. Если вызывается метод без котекста, или вместо него передается null, то в качестве контекста будет автоматически подставлен LogContext.Current.

Вот в очень сжатом виде механизм работы моего enterprise LogBook'а.

Подумываю завести проект на CodePlex. Займусь этим на досуге.

Удачи!