J.D. Meyer опубликовал Cheat-Sheet - каталог паттернов и guidance'ов, выпущеных Microsoft P'n'P. Рекомендую добавить в избранное, т.к. это отличная возможность систематизировать знания, тем более, что J.D. уже сделал это для нас! Вот линк: http://blogs.msdn.com/jmeier/archive/2008/10/09/cheat-sheet-patterns-practices-catalog-at-a-glance-posted-to-codeplex.aspx
Удачи!
пятница, 17 октября 2008 г.
System
среда, 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 компонент:
- контракта плагина и, если необходимо, хоста;
- представления хоста (то как хост видит плагин - изолирует хост и от контракта и от самого плагина подавно);
- представления плагина (то как плагин видит хост - -//-);
- адаптеры стороны хоста, транслирующие вызовы представления хоста в вызовы контракта - наследники представления хоста; адаптеры, транслирующие вызовы контракта в вызовы представления хоста - наследники контракта;
- адаптеры стороны плагина, транслирующие вызовы контракта в вызовы представления плагина - наследники контракта; адаптеры, транслирующие вызовы представления плагина в вызовы контракта - наследники представления хоста.
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;
Этот провайдер - ядро решения. Он занимается выведением зависимостей используя стандартный для 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-контейнере, например в 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, жить с которым намного легче. =) В конце я в качестве бонуса опубликую его код. А сейчас я хочу написать не об этом.
И вот, наконец, я обобщил и собрал воедино свое видение этой проблемы. В результате был написан контрол имитирующий 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.
Удачи!


