Книга MEF

    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.

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 36

    • UFO just landed and posted this here
        +2
        это вполне осуществимо, если будет материал, можно будет попробовать опубликовать
        +2
        Отлично! Всё понятно :) Спасибо большое.
          +2
          Отлично пишите =) С удовольствием почитаю и далее. Жалко вашу прошлую книгу купить не удалось из-за того, что не доставляют в Грузию российские магазины =(
            +2
            Оотлично! Очень понравилось спасибо! Приятный стиль изложения, и как про меня написано — сам придумывал велосипед для использования плагинов, используя рефлексию и бубен. Было бы здорово почитать еще, пишите!
              0
              Спасибо, порадовали! :) Если что, готов помогать в переводе, тема MEF мне знакома.
                0
                это не перевод, спасибо за предложение, занес вас в список :-) если что — обращусь
                  0
                  Есть много статей на английском. Если что, могу помочь с архитектурой MEF. Т.е. Как это все работает изнутри.
                    0
                    отписал в личку
                  0
                  Ну или с чем там еще -)
                  +2
                  Хорошо излагаете. Буду следить за циклом.
                  Спасибо!
                    +2
                    Когда я был маленький и глупый ещё не знал про рефлексию и ООП, но сделать плагины уже хотелось — меня всегда интересовало, откуда компилятор узнает про тип FirstPlugin, если он вынесен в плагин и при компиляции его просто нет?
                      0
                      А причем здесь рефлексия? Насколько я понимаю, просто сборка с плагином статически подключена к проекту. Или я ошибаюсь?
                        –1
                        внутри используется рефлексия, конечно, для поиска типов помеченных атрибутами и прочих дел
                          0
                          Это само собой. Вопрос был про то, откуда компилятор знает о FirstPlugin?
                            –1
                            ну он же лежит в том же проекте. плюс инстанцируется нами руками.
                            во второй главе будет более полезный и наглядный пример плагином который лежит в другой сборке
                              –1
                              Компилятор при сборке проекта не должен знать про типы его плагинов кроме общего интерфейса IPlugin, я считаю.
                                –1
                                во второй главе будет рассказано как это сделать
                                  0
                                  Прошло 2 года, второй главы все так и нет
                                    0
                                    :-) а вы памятливый, увы ее уже и не будет
                      0
                      я почему-то всегда проводил аналогию между MEF и OSGI…
                        +9
                        На мой взгляд, пример не очень удачный, поскольку экземпляр FirstPlugin создаётся в том же объекте, в котором используется. Это можно было просто присвоение написать: Plugin = new FirstPlugin(). А чтобы заменить FirstPlugin на SecondPlugin нужно изменять класс HomeController. Т.е просто не видно использования того механизма, ради котороно это всё, собственно, и нужно :o)
                          –1
                          для первой главы и введения, пример подходящий
                          про внешние плагины нельзя рассказать без рассказа про каталоги в MEF
                          собственно, во второй главе про это речь и пойдет, там будут и внешние плагины с более сложным примером :-)
                            +1
                            Хотел написать то же, что и Ordos.
                            Ждём продолжения.
                            Лишь однажды пользовался мефом и он понравился именно стандартизацией разработки.
                          0
                          Однако, автор этого текста настоятельно рекомендует указывать контракты всякий раз, как вы определяете импортируемые и экспортируемые части. Такое определение поможет вам быстрее понимать текст кода в дальнейшем.

                          А с другой стороны, это может привести к проблемам при изменении типа свойства без соответствующего изменения определения атрибута
                            0
                            экспортируемые части все равно помечены контрактом, без их изменения работать архитектура не будет. трудно представить, что разработчик поменял атрибуты у экспорта и не поменял у импорта

                            впрочем, это лишь рекомендация, если есть желание можно опускать контракт при импорте, правда не всегда
                            0
                            Думаю, следует добавить, что для начала надо добавить в References приложения сборку System.ComponentModel.Composition =)
                              0
                              именно, я в твиттере задал вопрос, найдет ли кто-нибудь то, что я пропустил? и вот нашел, спасибо за внимательность :-)
                              0
                              Вопрос: у нас продукт для .NET 2.0 — есть ли версия MEF для 2.0?
                                0
                                такой версии нет, MEF местами использует лямбды и другие фишки из 3.5
                                в принципе, наверняка, можно портировать, это же опенсорс, но это должен сделать тот кому это надо

                                есть альтернатива, которая, возможно подойдет
                                Mono.Addins

                                monoaddins.codeplex.com/
                                  0
                                  Только сегодня на работе обсуждали перевод одного из модулей с 2.0 на 3.5, вроде граблей быть не должно.
                                  Если не секрет, а у вас с чем связано то, что вы не переходите на 3.5?
                                    0
                                    У нас десктопный продукт — не хочется просить пользователей качать 3.5. Мы считаем, что у многих уже есть .NET 2 и, исходя из этого, используем его. Но более-менее адекватных сравнений не делали, на сколько я знаю. Если знаете, скажите.
                                      0
                                      Еще не переходят те, для кого критична поддержка Windows 2000
                                      0
                                      Можно взять любой IoC-контейнер и немного допилить.
                                        0
                                        MEF не IoC контейнер
                                      0
                                      Пример в ComposeParts() крайне неудачный и совсем не то, что должно фигурировать в первой статье.

                                      <code>
                                      

                                      Only users with full accounts can post comments. Log in, please.