Pull to refresh

Книга MEF

Reading time 7 min
Views 36K
imageЭтой статьей я начинаю цикл, цель которого – создание наиболее полного руководства по Managed Extensibility Framework (MEF) на русском языке. Результатом цикла, в моих планах, станет создание бесплатной электронной книги о MEF. Если у вас есть соображения или предложения по этому поводу – дайте мне знать в комментариях. Я надеюсь, что совместными усилиями мы можем создать отличное руководство.

Эта статья составлена по материалам моих докладов про MEF на разных встречах, в том числе на конференции DevConf.

Я ищу соавторов, критиков, просто людей, которые хотят помочь, в том числе с версткой документа.

Глава 1. Введение


Что такое MEF?


Managed Extensibility Framework – это плод работы нескольких человек в компании Microsoft по разработке инструмента позволяющего решать задачи расширяемости приложений. Проект изначально разрабатывался под свободной лицензией MS-PL с открытым исходным кодом. MEF развивался как отдельная библиотека для .NET 3.5 и был включен в .NET 4.0 как полноценная часть фреймворка. Включение MEF в стандартные библиотеки .NET – это важный шаг и признание значения этого инструмента.

Назначение MEF заключается в предоставлении возможности разработчику добавить в свое приложение возможность расширения функционала в время исполнения. Крайне распространенный вариант такой задачи – создание плагинов для программы. Используя MEF вы легко можете определить точки расширения вашего кода, а сторонний разработчик столь же легко напишет для вашего приложения отдельные расширения.

Основным идеологом и активистом MEF является Глен Блок (Glenn Block) – руководитель подразделения в команде .NET Framework в Microsoft. Множество материала по фреймворку вы можете обнаружить в блоге Глена на сайте http://codebetter.com/blogs/glenn.block/.

Главным источником информации по фреймворку и местом, где располагаются исходные коды проекта является сайт http://mef.codeplex.com/. Здесь вы обнаружите руководство разработчика, описание архитектуры MEF, ссылки на полезные ресурсы и обучающее видео, форумы, багтрекер. Последней версией MEF для .NET 3.5 является версия Preview 9.

MEF – молодой инструмент, но несмотря на это, уже представлено множество продуктов, которые используют его. Самым значительным продуктом из списка является Visual Studio 2010, которая использует MEF как внутри себя (дизайнеры UML и Entity Framework), так и для предоставления API расширения сторонним разработчикам. Другие примеры использования MEF: Silverlight Analytics Framework, Silverlight Media Framework, TypeMock Test Lint, RavenDB, Caliburn, Common Service Locator.

Стоит заметить, что в связи с тем, что MEF – это проект со свободной лицензией, фреймворк был успешно перенесен на Mono – альтернативную реализацию .NET с открытым исходным кодом от компании Novell.

Назначение MEF


Какие же задачи призван решить MEF? На сегодняшний момент в .net-среде не существовало единообразного инструмента для решения задач расширения приложений. Потому, когда такая задач возникала, каждый разработчик решал ее по-своему, в меру своих знаний, умений, требований задачи. Вполне очевидно, что эта ситуация приводит к созданию кода, который архитектурно (или принципиально) несовместим друг с другом. То есть, перейдя из одной компании в другую вы можете обнаружить совершенно иной подход в решении одной и той же задачи. На рис.1 представлен такой итог: множество проектов с разными вариантами и конфигурацией расширения.

image
Рис.1. Проблема

MEF нацелен на преодоление этой проблемы. Фреймворк, включенный в .NET 4.0 предлагает единый способ решения архитектурных задач расширяемости приложения. Достижение цели MEF – распространение единообразного подхода – позволит упростить нам разработчикам жизнь и сделать сопровождение чужого кода или написание расширений к чужим приложения значительно проще и в знакомой (закономерной) манере (рис.2).

image
Рис.2. Решение

В идеальной перспективе, разработчик, который изучил MEF, будет способен (без особых сложностей и длительного изучения архитектуры) разрабатывать компоненты для всевозможных проектов на платформе .NET, написанных любыми другими компаниями или отдельными людьми. И, таким образом, MEF способен решить задачу взаимопонимания между разработчиками, предлагая общий язык общения.

Основы


Залог состоявшейся успешности MEF как инструмента кроется в его простоте. MEF построен всего на трех функциональных частях: импорт, экспорт и композиция. Используя импорт вы характеризуете части вашего приложения как способные к расширяемости. Сторонний разработчик, используя функции экспорта, создает отдельный компонент (часть, плагин), предназначенный для вашего приложения. И, в ходе выполнения, вы используете функции композиции для того чтобы соединить части импорта с частями экспорта. Рассмотрим эти этапы подробнее.

Импорт


На рис. 3 представлено определение некоторой импортируемой с помощью MEF части кода.

image
Рис. 3. Импортируемая часть

Здесь определяется автоматическое свойство, тип которого определяется неким интерфейсом IPlugin. С помощью атрибута Import, который является частью инфраструктуры MEF, свойство помечается как импортируемое. Само свойство таким образом становится частью импорта, а типом части будет являться интерфейс IPlugin.

Обратите внимание на параметр атрибута Import: typeof(IPlugin) в данном случае определяет так называемый контракт MEF. Контактом называется уникальный идентификатор, который однозначно определяет часть иморта, часть экспорта и таким образом позволяет MEF соединить обе части в процессе композиции. Проще говоря, определяя контракт вы сообщаете некий пароль, который должна назвать часть расширения для того, чтобы присоединиться к точке импорта. Далее в этой главе контракты будут рассмотрены более подробно.

Экспорт


На рис. 4 дано определение некоего класса, который реализует экспортируемую часть расширения в MEF.

image
Рис. 4. Экспортируемая часть

Здесь определяется некий класс FirstPlugin, который реализует интерфейс IPlugin (часть импорта в предыдущей теме определена с помощью него же). С помощью атрибута Export из инфраструктуры MEF класс помечается как экспортируемая часть (можно сказать “плагин”). Обратите внимание, параметром атрибута Export служит контракт объявленный как typeof(IPlugin). Точно такой же контракт был определен в части импорта в предыдущей теме.

Определение одинакового контракта при импорте и экспорте позволяет MEF находить предназначенный друг-другу части.

Композиция


После определения импортируемых и экспортируемых частей необходимо произвести их композицию (рис. 5). Композицией называет процесс поиска всех определенных частей MEF, их инстанцирования и присвоения экземпляров экспортируемых частей частям импорта. Иными словами, в процессе композиции плагины помеченные атрибутом экспорта подключаются к частям вашего когда, помеченными атрибутами импорта.

image
Рис. 5. Композиция

Здесь создается экземпляр контейнера композиции (контейнер – это часть инфраструктуры MEF, подробнее будет рассмотрена далее). После чего, у контейнера вызывается метод ComposeParts, параметры которого представляют собой перечисление элементов, в которых MEF должен искать части для композиции. В данном случае, this – это экземпляр текущий класса и new FirstPlugin() – это инстанцированный плагин, помеченный нами в предыдущей части атрибутом Export.

После вызова ComposeParts, в контейнере container будут записаны экземпляры this и FirstPlugin, а ипортируемая часть Plugin (рис. 3) получит значение экземпляра FirstPlugin (рис. 4). Чуть далее мы рассмотрим весь процесс вместе в примере кода.

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

Контракты


Контракты в инфраструктуре MEF играют важную связующую роль между частями импорта и экспорта. Контракты обязательно явно или неявно определяются при импорте или экспорте частей. На рис. 3 в качестве контракта используется выражение typeof(IPlugin), которое уникально определяет тип интерфейса IPlugin.
На самом деле инфраструктура MEF содержит несколько возможностей определение контракта при импорте (Таблица 1).

Таблица 1. Варианты определения контрактов при импорте
ImportAttribute(Type) с помощью указания передачи типа (так как мы рассматривали)
ImportAttribute(String) с помощью передачи имени контракта в виде строки — в этом случае, вы должны обязательно гарантировать уникальность такой строки среди других контрактов
ImportAttribute(String, Type) с помощью передачи как имени контракта в виде строки, так и его типа – это может оказаться полезным, когда появляется потребность создать несколько разных контрактов для одного и того же типа
ImportAttribute() в случае, если атрибутам импорта (Import и другие) не был передан тип контракта, то он будет определен автоматически на основании типа к которому этот атрибут применяется. Таким образом, вы можете опустить параметр typeof(IPlugin) в примере на рис. 3.
В вариантах когда имя контракта не было передано оно формируется автоматически с помощью метода GetContractName, который возвращает полное строковое определение типа включая его пространство имен. Как уже упоминалось, если не указан тип контракта, то он так же получается автоматически.

Для атрибутов экспорта действуют те же правила, что и при импорте. Но при определение экспорта с помощью атрибутов Export и других важно понимать следующее поведение: в случае, если не указан тип и имя контракта они будут получены автоматически на основании типа элемента к которому применяется атрибут. Иными словами, если в примере на рис. 4 опустить параметр typeof(IPlugin), то инфраструктура MEF определит контракт автоматически на основании типа FirstPlugin, но не IPlugin, как нам того требуется. Это означает, что если вы строите экспортируемую часть на основе базовых интерфейсов или классов, то вам необходимо явно указывать для контракта его тип.

Hello, MEF!


Пришло время собрать все знания данной главы и реализовать их в конкретном примере. Для демонстрации создадим проект на базе ASP.NET MVC Framework 2 и определим в нем следующий интерфейс:

image


Этот интерфейс будет определять тип наших импортируемых и экспортируемых частей.
Затем, в контроллере HomeController определим точку импорта:

image

Обратите внимание, мы определили точку импорта с контрактом typeof(IPlugin). Однако в этом случае, мы вполне можем опустить это определение контракта, доверив его автоматическое определение инфраструктуре MEF.
Однако, автор этого текста настоятельно рекомендует указывать контракты всякий раз, как вы определяете импортируемые и экспортируемые части. Такое определение поможет вам быстрее понимать текст кода в дальнейшем.
Следующим шагом определим экспортируемую часть, можно назвать ее плагин, которая будет “подключаться” к определенному выше импорту.

image

Обратите внимание, если в случае определения импорта мы могли опустить контракт, то в этом случае с экспортом, контракт мы опустить не можем, так как реализуем экспортируемую часть от интерфейса, который участвует в контракте. Если мы опустим определение контракта в этом коде, то получим исключение во время выполнение от инфраструктуры MEF, которая не сможет найти для точки импорта с контрактом typeof(IPlugin) подходящую экспортируемую часть.

Последним нашим шагом будет написание кода композиции, который мы поместим в конструктор HomeController.

image

После запуска полученного кода мы можем наблюдать ожидаемый результат (рис. 6).

image
Рис. 6. Результат работы MEF

Как вы можете видеть, после выполнения ComposeParts, свойство Plugin приняло значение экземпляра класса FirstPlugin, как и было задумано.

Заключение


В этой главе мы познакомились с фреймворком MEF, его назначением и проблемами на решение которых он нацелен. Мы рассмотрели базовые понятия MEF и некоторые их особенности: импорт, экспорт, композиция, контракты. Мы построили первый пример на базе полученной информации, который продемонстрировал работу MEF на деле.

В следующей главе мы углубимся во фреймворк, рассмотрим более сложный пример с несколькими плагинами и познакомимся с понятием каталогов в MEF.
Tags:
Hubs:
+57
Comments 36
Comments Comments 36

Articles