Модель дополнений
111WPF --- Периферия WPF --- Модель дополнений
Дополнения (add-ins, также называемые подключаемыми модулями (plug-ins)) — это отдельно компилируемые компоненты, которые приложение может находить, загружать и использовать динамически. Часто приложение спроектировано с учетом использования дополнений, так что оно может быть расширено в будущем без необходимости в модификации, перекомпиляции и повторном тестировании.
Дополнения также предлагают гибкость в настройке отдельных экземпляров приложения для определенного рынка или клиента. Но наиболее частой причиной для использования модели дополнений является необходимость позволить независимым разработчикам расширять функциональность приложения. Например, дополнения к продуктам Adobe предлагают широкий спектр различных плагинов, расширяющих базовые возможности программ. Дополнения подключаются через специальный менеджер расширений:

Дополнения к Google Chrome предоставляют расширенные средства веб-серфинга и совершенно новую функциональность:

В обоих случаях такие дополнения создаются независимыми разработчиками.
Со времен .NET 1.0 разработчики были вынуждены разрабатывать собственные системы дополнений. Два базовых ингредиента таких систем — это интерфейсы (позволяющие определять контракты, через которые приложение взаимодействует с дополнением, а дополнение — с приложением) и рефлексия (предоставляющая приложению возможность динамически обнаруживать и загружать дополнительные типы из отдельной сборки).
Однако построение системы дополнений с нуля требует значительного объема работы. Понадобится продумать способ нахождения дополнений, а также обеспечить правильное управление ими (другими словами, чтобы они выполнялись в ограниченном контексте безопасности и при необходимости могли быть выгружены).
К счастью, теперь .NET включает предварительно построенную модель дополнений, которая избавляет от забот подобного рода. Эта модель использует интерфейсы и рефлексию, подобно модели дополнений, которую вы, возможно, разрабатывали самостоятельно. При этом модель дополнений .NET берет на себя заботу о низкоуровневых механизмах выполнения таких утомительных задач, как обнаружение и размещение.
Выбор между MAF и MEF
Прежде чем приступить к построению расширяемого приложения с дополнениями, придется решить одну неожиданную проблему. Дело в том, что .NET включает не одну платформу дополнений, а две.
В .NET 3.5 появилась модель дополнений по имени Managed Add-In Framework (MAF). Но чтобы сделать картину более интересной (и сильнее ее запутать), в .NET 4 была добавлена новая модель под названием Managed Extensibility Framework (MEF).
Разработчики, которым еще не так давно приходилось создавать собственную систему дополнений, вдруг получили две совершенно отдельные технологии дополнений, разделяющий один и тот же фундамент. Так в чем же разница между ними?
Из двух платформ MAF является более устойчивой. Она позволяет отделить дополнения от приложения, так что они не зависят ни от чего, кроме определенного интерфейса. Это обеспечивает желаемую гибкость в сценариях с поддержкой множества версий, например, если нужно изменить интерфейс, но продолжать поддерживать старые дополнения с целью обратной совместимости. MAF также позволяет приложению загружать дополнения в отдельный домен приложений, так что если они вдруг потерпят крах, это не затронет основное приложение. Все эти особенности означают, что MAF работает хорошо, если одна команда разработчиков занимается приложением, а другая (или несколько) — его дополнениями. Кроме того, MAF особенно хорошо подходит для поддержки дополнений от независимых поставщиков.
Однако средства MAF имеют свою цену. MAF — сложная платформа, и настройка конвейера дополнения утомительна даже для простого приложения. И здесь на сцену выходит MEF. Это более легковесный вариант, который предназначен для того, чтобы сделать расширяемость настолько простой, как копирование связанных сборок в одну папку. Однако MEF построен на другой основополагающей философии, чем MAF.
В то время как MAF представляет собой строгую, управляемую интерфейсами модель дополнений, MEF — более свободная система, позволяющая строить приложение в виде коллекции частей. Каждая часть может экспортировать функциональность, и любая часть может импортировать функциональность любой другой части. Эта система обеспечивает разработчикам намного более высокую степень гибкости, и особенно хорошо подходит для построения составных приложений (модульных программ, разрабатываемых одной командой разработчиков, но которые должны собираться разными способами, с различным набором реализованных средств для разных выпусков).
Очевидная опасность состоит в том, что MEF слишком свободна, и плохо спроектированное приложение может быстро превратиться в клубок взаимозависимых частей.
Платформа MEF более подробно описана на сайте сообщества MEF.
Если же в действительности интересуют не дополнения, а составные приложения, то стоит обратить внимание на библиотеку Composite Application Library (CAL) от Microsoft, известную также под ее прежним названием — Prism.
Хотя MEF — это решение общего характера для построения любого рода модульных приложений .NET, библиотека CAL специально ориентирована на WPF. Эта библиотека включает средства, связанные с пользовательским интерфейсом, такие как возможность разрешения различным модулям взаимодействовать с событиями и отображать содержимое в отдельных областях. CAL также поддерживает "гибридные" приложения, которые могут компилироваться для платформы WPF или основанной на браузерах платформы Silverlight. Документация и загружаемые файлы CAL доступны по адресу Prism, а ознакомительная статья — по адресу Prism: Patterns For Building Composite Applications With WPF.
Конвейер дополнения
Ключевое преимущество модели дополнений состоит в том, что она избавляет от необходимости написания низкоуровневых задач, таких как обнаружение. Ключевым ее недостатком следует считать высокую сложность. Проектировщики .NET приложили немало усилий, чтобы сделать модель дополнений достаточно гибкой, и она справляется с разнообразными сценариями поддержки множества версий и размещения.
В результате получилось, что для реализации модели дополнений в приложении должны быть созданы как минимум семь отдельных компонентов, даже если не планируется использовать ее наиболее сложные средства.
Ядром модели дополнений является конвейер (pipeline) дополнения, представляющий собой цепь компонентов, которые позволяют принимающему приложению взаимодействовать с дополнением. На одном конце этого конвейера находится принимающее приложение (хост). На другом конце — дополнение. Между ними находятся пять компонентов, которые отвечают за взаимодействие:

На первый взгляд эта модель кажется несколько перегруженной. Более простой сценарий мог бы помещать единственный уровень (контракт) между приложением и дополнением. Однако дополнительные уровни (представления и адаптеры) позволяют модели дополнений быть более гибкой в определенных ситуациях.
Как работает конвейер
Контракт — это краеугольный камень конвейера дополнения. Он включает в себя один или более интерфейсов, которые определяют, как принимающее приложение может взаимодействовать с дополнениями и как дополнения могут взаимодействовать с принимающим приложением. Сборка контракта может также включать специальные сериализуемые типы, которые планируется использовать для передачи данных между принимающим приложением и дополнением.
Конвейер дополнения спроектирован с учетом необходимой расширяемости и гибкости. И по этой причине принимающее приложение и дополнение не используют контракт напрямую. Вместо этого они применяют собственные соответствующие версии контракта, называемые представлениями (views). Принимающее приложение использует представление хоста, в то время как дополнение — представление дополнения.Обычно представление включает абстрактные классы, которые соответствуют интерфейсам в контракте.
Хотя обычно они достаточно похожи, контракты и представления полностью независимы. Соединить эти две части вместе — задача адаптеров. Адаптеры выполняют это соединение, представляя классы, которые одновременно наследуются от классов представлений и реализуют интерфейсы контракта. На рисунке ниже показано это проектное решение:

По сути, адаптеры заполняют пробел между представлениями и интерфейсом контракта. Они отображают вызовы представления на вызовы интерфейса контракта. Также они отображают вызовы интерфейса контракта на соответствующий метод представления. Это несколько усложняет проектное решение, но добавляет дополнительный уровень гибкости.
Чтобы понять, как работают адаптеры, рассмотрим, что происходит, когда приложение использует дополнение. Сначала принимающее приложение вызывает один из методов в представлении вида. Но вспомните, что представление хоста — это абстрактный класс. "За кулисами" приложение на самом деле вызывает метод хост-адаптера через представление хоста. (Это возможно, поскольку класс адаптера хоста наследуется от класса представления хоста.) Хост-адаптер затем вызывает соответствующий метод в интерфейсе контракта, который реализован адаптером дополнения. И, наконец, адаптер дополнения вызывает метод в представлении дополнения. Этот метод реализован дополнением, которое и выполняет реальную работу.
Более развитые адаптеры
Если нет особых потребностей в поддержке версий или хостинге, то адаптеры будут совершенно прямолинейны. Они просто передают работу по конвейеру. Однако адаптеры также являются важной точкой расширяемости для более изощренных сценариев.
Примером может служить поддержка версий. Очевидно, что можно независимо обновлять приложение или его дополнения, не меняя способа их взаимодействия — до тех пор, пока используются одни и те же интерфейсы в контракте. Однако в некоторых случаях может понадобиться изменить интерфейсы, чтобы ввести новые средства. Это представляет некоторую проблему, потому что старые интерфейсы должны поддерживаться с целью обратной совместимости со старыми дополнениями. После нескольких ревизий получится сложная смесь похожих, но разных интерфейсов, и приложение должно будет распознавать и поддерживать их все.
Благодаря модели дополнений, можно применить другой подход к обратной совместимости. Вместо ввода множества интерфейсов можно использовать единственный интерфейс в контракте и применять адаптеры для создания различных представлений. Например, дополнение версии 1 может работать с приложением версии 2 (которое представляет контракт версии 2) до тех пор, пока имеется адаптер дополнения, заполняющий пробел. Аналогично, если разрабатывается дополнение, которое использует контракт версии 2, его можно применять с исходной версией 1 приложения (и версией 1 контракта) за счет использования различных адаптеров дополнений.
Аналогичный подход можно применить, когда есть специальные потребности хостинга. Например, можно использовать адаптеры для загрузки дополнений с разными уровнями изоляции, или даже разделять их между приложениями. Принимающее приложение и дополнение не обязаны знать об этих деталях, потому что их урегулируют адаптеры.
Даже если не нужно создавать специальные адаптеры для реализации специализированных стратегий поддержки версий и хостинга, все равно эти компоненты должны быть включены. Однако все дополнения могут использовать одинаковые представления и адаптерные компоненты. Другими словами, решив проблему настройки полноценного конвейера для одного дополнения, затем можно добавлять новые дополнения, не прикладывая дополнительных усилий.
Структура каталогов дополнений
При использовании конвейера дополнения необходимо придерживаться строгой структуры каталогов. Это отдельная от приложения структура каталогов. Другими словами, вполне допустимо располагать приложение в одном месте, а все дополнения и компоненты конвейера — в другом. Однако компоненты дополнений должны быть организованы в специально именованных подкаталогах относительно друг друга.
Например, если система дополнений использует корневой каталог c:\MyApp, то понадобятся следующие подкаталоги:
c:\MyApp\AddInSideAdapters
с:\MyApp\AddInViews
c:\MyApp\Contracts
c:\MyApp\HostSideAdapters
c:\MyApp\AddIns
Подкаталог AddIns (последний в списке) должен иметь отдельный подкаталог для каждого дополнения, используемого приложением, например, c:\MyApp\AddIns\MyFirstAddIn, c:\MyApp\AddIns\MySecondAddIn и т.д.
В этом примере предполагается, что исполняемый модуль приложения развернут в каталоге c:\MyApp. Другими словами, один и тот же каталог выполняет двойную функцию — как папка приложения, и как корень дополнений. Это распространенный вариант развертывания, хотя и не обязательный.
Если вы внимательно рассмотрели диаграммы конвейеров, то должны были заметить, что существует подкаталог для каждого компонента кроме представлений стороны хоста. Это объясняется тем, что представления хоста используются напрямую принимающим приложением-хостом, поэтому они развертываются вместе с исполняемым приложением. (В данном примере это означает, что они находятся в c:\MyApp.)
Представления дополнений так не развертываются, поскольку существует вероятность того, что несколько дополнений будут использовать одно и то же представление дополнений. Благодаря выделенной папке AddInViews, понадобится развернуть (и обновить) только одну копию каждой сборки представления дополнения.