MEF

Сегодня я хочу рассказать о системе постороения динамического, модульного приложения с использованием Managed Extensibility Framework (MEF).  Вообще это уже достаточно старая технология, которая вылилась из системы Add-On от компании Микрософт. МEF стала дружелюбной оболочкой над монструозной системой предложенной ранее. Результат оказался настолько хорош, что MEF включили в поставку .Net Framework 4 по умолчанию, так что не потребуется искать и загружать библиотеки отдельно, беспокоится, есть ли данная библиотека у пользователя.

Так же уже понятно, что данную технологию можно использовать в Enterprise разработке, так как она в основной поставке фреймворка, а это такой аргумент, против которого менеджеры проектов не попрут. Обычно, менеджеры проектов очень неохотно идут на использование технологий и продуктов которые не упомянуты в пресс-релизах MS, даже если данная технология широко и успешно используется сообществом. Хотя их можно понять, это будет первый пункт, по которому их будут пинать в случае любых накладок.

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

 

MEF можно использовать начиная с версии .Net 3.5, взяв необходимые библиотеки с CodePlex. Как вы уже догадались, оттуда же можно взять исходный код и посмотреть как все это работает.

Основные возможности MEF:

  • Предоставляет стандартизированный способ для организации мастер-приложения и для дополнений к нему. В общем случае дополнения не привязаны к конкретному приложению и при реализации общего интерфейса могут быть использованы разными программами, что в целом не отменяет и жесткой привязки к мастер-приложения. Дополнения могут быть зависимы друг от друга и MEF самостоятельно подключит их в нужном порядке.
  • Предоставляет возможности поиска и загрузки дополнений силами самого MEF
  • Позволяет помечать дополнения метаданными, для дополнительных возможностей по подключению и фильтрации дополнений.

Создание простого приложения

Для начала сделаем что-то в духе HelloWorld приложения, а потом сделаем что-то более полезное, так как я бы сам себя проклял, если бы остановился на примере HelloWorld которое обычно ничего не демонстрирует ))

Итак, приложение будет самое простое которое только может быть. Пусть оно будет консольное и будет выводить фразу на разных языках, в зависимости от того, какую библиотеку мы подуснем приложению. Для начала самое оно!  Можно будет сосредоточится только на MEF коде.

Создаем консольное приложение. Оно будет мастер-приложением. Для того, чтобы подключать и узнавать другие библиотеки выделим интерфейс коммуникации в дополнительную сборку. Итак, на данном этапе у нас будет консольный проект HelloConsole и сборка LangDefiniton.

К проекту HelloConsole добавляем ссылку на сборку System.ComponentModel.Composition и на нашу библиотеку LangDefinition. К данному моменту решение должно выглядеть следующим образом:

В проекте LangDefinition создаем новый интерфейс, где определим метод SayHello, который будет возвращать нужный текст.

 

После того, как определили интерфейс, можно написать код, который будет подгружать и использовать дополнения к нашему приложению. Для этого переходим в проект HelloConsole и там создаем класс AddingComposer.

Метод, который отвечает за обнаружение и подгрузку дополнений будет LoadAddings. В нем будем указывать в какой директории искать дополнения и подгружать их.

 

На третьей строке указывается, где именно искать дополнения. В данном случае указан провайдер DirectoryCatalog, который позволяет указать путь до директории с дополнениями (абсолютные или относительные пути от исполняемой библиотеки, в данном случае исполняемого файла), в том числе указать фильтры поиска библиотек.

Так же можно указывать сборки (Assembly), где искать дополнения. Для этого надо будет создать AssemblyCatalog. Кроме этого можно указать конкретно типы, которые надо будет импортировать. Для этого используйте TypeCatalog.

На пятой строке указывается что и каким образом собирать в основное приложение. Можно помечать дополнения как потокобезопасные.

Чтобы получить доступ к найденому дополнению, необходимо выставить поле с нашим интерфейсом и пометить его специальным аттрибутом Import, который укажет системе что это импортируемый из дополнения тип. Итого класс будет выглядеть таким образом:

 

На данный момент все готово, к тому, чтобы написать собственно код дополнения.

 

Добавляем в проект новую библиотеку классов. Назовем ее RussianHello. Ссылаемся в ней на проект с интерфейсом, на System.ComponentModel.Composition и реализуем интерфейс ILanguage.

 

 

Для  того, чтобы MEF увидел класс, надо его пометить аттрибутом Export.В данном случае можно было не конкретизировать интерфейс экспорта.

Еще один момент, для перед проверкой приложения. Будет полезно указать папку формирования библиотеки сразу на Addings в директории, где будет лежать исполняемый файл.

Финальным действием будет вызов класса  AddingComposer в теле Main.

 

После этого запускаем приложение и видим:

 

Это базовые возможности MEF, на которых многие статьи и заканчиваются, но мы конечно пойдем дальше, последовательно развивая приложение.

Добавляем еще один язык

Создаем еще одну сборку, где реализуем интерфейс ILanguage для английского языка.

 

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

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

Для разрешения этой ситуации будем использовать метаданные для экспорта приложений.

Метаданные для экспорта

Метаданные для экспорта могут быть нетипизированные и типизированные. Начнем с нетипизированных, как более простых в использовании.

Слабая типизация

Метаданные добавляются с помощью специального аттрибута ExportMetadata. Нам потребуется конструктор, где можно задавать имя переменной и ее значение, которое будет использоваться для однозначной идентификации дополнения.

Для класса RuHello добавим

 

Тогда получится

 

Аналогично дополним класс EnHello, чтобы получилось

 

После этого можно будет выбирать сборки по полю Lang. Саму выборку будем осуществлять в мастер-приложении.

Для множественного импорта используется аттрибут ImportMany с указанием типа для импорта. Этот аттрибут лучше навешивать на ленивую коллекцию из MEF пространства имен, для отложенной инициализации компонентов.

 

Выбор нужного дополнения можно осуществлять следующей выборкой:

 

После этого основной код приложения будет выглядеть как:

 

Можно пробовать вводить значения Ru и En и видеть как приложение нам выводит данные из нужного дополнения.

Самое приятное, что до запуска приложения можно вообще очистить папку Adding, и копировать туда дополнения по мере работы. Они будут подхватываться без перезапуска приложения.

Строгая типизация

Теперь попробуем все то же самое с типизированными метаданными.

В общем алгоритм выглядит следующим образом: объявляем класс, который будет типизированным аттрибутом, наследуем его от ExportAttribute. Создаем интерфейс, который будет повторять набор свойств, но только на чтение. Используем вновь созданный аттрибут на нужном классе.

Допустим что у нас метаданные разрослись до такого состояния:

 

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

 

Класс наследуется от ExportAttribute, при этом к нему применяются атрибуты MetadataAttribute и указывается с какими типами он будет использоваться и как. В данном случае аттрибут должен применятся только к классам, при этом множественное применение через наследование запрещено.

Теперь надо создать интерфейс который будет повторять набор свойств, причем они должны быть объявлены только на чтение.

 

Интерфейс можно объявить рядом с аттрибутом.

Далее можно применять аттрибут. Это выглядит более симпатично, нежели нетипизированные метаданые.

 

Премущества такого подхода очевидны:

  • Нет возможности сделать опечатку, компилятор заметит;
  • Точно известно какие параметры могут быть, intelliSense подскажет;
  • Легко изменять и искать по проекту использование свойств;
  • Вменяемый фильтр при поиске дополнений к приложению.

Осталось только модифицировать мастер-приложение, чтобы оно понимало строготипизированные метаданные.  Список объявим как словарь из нужного нам интерфейса и интерфейса метаданных.

 

Обращаю внимание, что у атрибута ImportMany не указан тип, который надо загружать. Если оставить тип, то дополнения подгрузятся два раза и MEF не сможет корректно разрешить зависимости. Надо указать тип либо в ImportMany, либо в Export, но не в двух местах одновременно.

Метод GetHello теперь изменится, и на мой взгляд станет более информативным и аккуратным

 

Хм… не очень пока получается сложные примеры показать, да? Ну так это наверно потому что не особенно сложные вещи и рассматривали. Остальное в следующей части, а то и так тут слишком много букв. Ждите, она уже почти готова ;)

 

Hard’n’heavy!

Оставить комментарий