Sunday, May 11, 2014

Введение в Prism 5. Работа с загрузчиком MefBootstrapper

В этой статье мы рассмотрим использование библиотеки Managed Extensibility Framework (MEF) с таким замечательным наборов компонентов, как Prism 5. Напишем пример загрузчика, который будет реализован на основе MefBootstrapper. Загрузчик необходим для того, чтобы инициализировать различные компоненты и службы Prism. Совсем недавно мной была рассмотрена возможность использования bootstrapper в статье "Введение в Prism 5. Bootstrapper"В указанной статье рассмотрен UnityBootstrapper, использующий в качестве контейнера инициализации  IoC контейнер Unity.
Небольшая информация для тех, кто уже работал раньше с Prism 4.1, по поводу загрузчиков UnityBootstrapper и MefBootstrapper. Между версией Prism 4.1 и 5 в документации изменений не произошло, если вы читали перевод документации по Prism 4.1, например, на habrahabr.ru. Изменение только немного затронуло сборки и пространства имен. Раньше один и второй загрузчик по умолчанию входили в Prism, и можно было выбрать тот, который нам нужно. Сейчас же данные загрузчики поставляются отдельно, и скачать их можно через NuGet Packages
Приведу с habrahabr некоторые ключевые моменты для демонстрации отличия двух этих загрузчиков. По поводу варианта использования одного либо второго загрузчика рекомендую смотреть документацию.
Библиотека Prism предоставляет два DI контейнера по умолчанию: Unity и MEF. Prism расширяема, таким образом, вы можете использовать другие контейнеры, написав небольшое количество кода для их адаптации. И Unity, и MEF обеспечивают одинаковую основную функциональность, необходимую для внедрения зависимостей, даже учитывая то, что они работают сильно по-разному. Некоторые из возможностей, предоставляемые обоими контейнерами:
  • Оба позволяют регистрировать типы в контейнере.
  • Оба позволяют регистрировать экземпляры в контейнере.
  • Оба позволяют принудительно создавать экземпляры зарегистрированных типов.
  • Оба внедряют экземпляры зарегистрированных типов в конструкторы.
  • Оба внедряют экземпляры зарегистрированных типов в свойства.
  • У них обоих есть декларативные атрибуты для управления типами и зависимостями.
  • Они оба разрешают зависимости в графе объектов.
Unity предоставляет несколько возможностей, которых нет в MEF:
  • Разрешает конкретные типы без регистрации.
  • Разрешает открытые обобщения (Generics).
  • Может использовать перехват вызова методов для добавления дополнительной функциональности к целевому объекту (Interception).
MEF предоставляет несколько возможностей, которых нет в Unity:
  • Самостоятельно обнаруживает сборки в каталоге файловой системы.
  • Загружает XAP файлы и ищет в них сборки.
  • Проводит рекомпозицию свойств и коллекций при обнаружении новых типов.
  • Автоматически экспортирует производные типы.
  • Поставляется вместе с .NET Framework, начиная с четвёртой версии.
Два этих контейнера работают по-разному. Если ваша работа в основном состоит из разработки разных расширений под ваш продукт, иными словами, плагинов, то вам, наверное, больше подойдет загрузчик, который работает с MEF. Если же для вам нужно просто связать те компоненты, информация о которых вам известна, то вам подойдет загрузчик, который работает с Unity.
Примечание: загрузчик с помощью MEF работает сам по себе не слишком быстро. Это происходит из-за специфики работы, которая на него ложится. Если вам нужно подтянуть нужные плагины, то можете посмотреть в сторону таких IoC контейнеров, как Autofac (загрузчик AutofacBootstrapper) и StructureMap (загрузчик StructureMapBootstrapper), у которого есть функция Scan, с помощью которой можно сканировать сборки. 
Приступим к созданию нашего WPF-приложения с использованием Prism 5 и загрузчика MefBootstrapper. Назовем наше приложение MefBootstrapperSample.
Нужно указать .NET Framework 4.5, так как Prism 5 работает, начиная только с версии фреймворка 4.5. Следующим этапом необходимо установить Prism 5 и Prism.MefExtensions. Сделать это мы можем с помощью NuGet Packages.
Мы будем оперировать терминами Prism. Основное окно программы, которое отображает всю информацию, называется оболочкой (Shell). Поэтому мы также переименуем наше окно с MainWindow.xaml в Shell.xaml. Затем перейдем в файл Shell.xam.cs и изменим класс MainWindow на Shell.
public partial class Shell : Window
{
       public Shell()
       {
             InitializeComponent();
       }
}
Чтобы метод InicializeComponent не подсвечивался красным, необходимо перейти в Shell.xaml и изменить название класса с MainWindow на Shell.
x:Class="MefBootstrapperSample.Shell"
Для пущей уверенности можно выполнить поиск по всему проекту и выполнить замену для всех названий. Следующим этапом удалим наше главное окно, чтобы оно не стартовало с App.xaml с помощью метода StartupUri. Удаляем строку с надписью
StartupUri="MainWindow.xaml"
Запуском нашего приложения будет заниматься наш загрузчик. Чтобы наша оболочка имела приемлемый вид, добавим в нее что-либо.
<Window x:Class="MefBootstrapperSample.Shell"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MefBootstrapper example" Height="350" Width="525">
    <Grid>
        <TextBlock>Hello MefBootstrapper</TextBlock>
    </Grid>
</Window>
Затем добавим непосредственно сам загрузчик нам в проект.
Наследуем наш класс Boostrapper от класса MefBootstrapper с пространства имен Microsoft.Practices.Prism.MefExtensions.
public class Bootstrapper : MefBootstrapper
{
       protected override DependencyObject CreateShell()
       {
             throw new NotImplementedException();
       }
}
Следующим делом создадим наше окно в методе CreateShell(). Только у нас есть маленькая проблема, которую вы можете увидеть на рисунке.
Тот же загрузчик, который использует IoC контейнер Unity, автоматически разруливает зависимость и добавляет в проект  библиотеку Unity. В загрузчике MEF нам нужно добавить его вручную, так как он идет в поставке с .NET Framework, плюс он может обновляться параллельно, так как проект MEF сделали открытым. Список открытых проектов вы можете посмотреть по ссылке dotnetfoundation.org. Добавляем в наш проект через Add Reference ссылку на System.ComponentModel.Composition.
После того как мы добавим ссылку на MEF, наша функция станет доступной.
protected override DependencyObject CreateShell()
{
       return Container.GetExportedValue<Shell>();
}
Только она не будет работать. Первая причина заключается в том, что для того чтобы MEF знал, что мы хотим подтянуть нашу оболочку Shell, нам нужно пометить ее атрибутом [Export], иначе MEF не сможет ее найти. Измененный класс Shell приведен ниже.
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
[Export]
public partial class Shell : Window
{
       public Shell()
       {
             InitializeComponent();
       }
}
Единственное, что в нем изменилось, ‒ это то, что теперь он помечен атрибутом Export. Если вы не знаете принцип работы MEF, рекомендую начать из основ ("Пишем свой плагин на MEF"), чтобы понять, как работает экспорт, импорт и композиция. 
Возвращаемся к нашему загрузчику и добавим отображение нашей оболочки в методе InitializeShell().
protected override void InitializeShell()
{
       Application.Current.MainWindow = (Window)Shell;
       Application.Current.MainWindow.Show();
}
До этой части наш пример работал практически так же, как пример, который был приведен ранее для UnityBootstrapper. Пока отличие у нас заключается в методах. В Unity код был такой:
Container.Resolve<Shell>();
А в Mef стал такой:
Container.GetExportedValue<Shell>();
В MEF мы также обязаны помечать атрибутами все те классы, которые хотим использовать. И теперь еще одно важное отличие MEF: нам необходимо сконфигурировать каталог, чтобы наш загрузчик знал, где что искать. Для этого нужно переопределить метод ConfigureAggregateCatalog().
protected override void ConfigureAggregateCatalog()
{
       base.ConfigureAggregateCatalog();
       AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(Bootstrapper).Assembly));
}
Полный код нашего класса Bootstrapper приведен ниже.
public class Bootstrapper : MefBootstrapper
{
       protected override DependencyObject CreateShell()
       {
             return Container.GetExportedValue<Shell>();
       }

       protected override void InitializeShell()
       {
             Application.Current.MainWindow = (Window)Shell;
             Application.Current.MainWindow.Show();
       }

       protected override void ConfigureAggregateCatalog()
       {
             base.ConfigureAggregateCatalog();
             AggregateCatalog.Catalogs.Add(new AssemblyCatalog(typeof(Bootstrapper).Assembly));
       }
}
Теперь осталось использовать наш созданный класс. Для этого перейдем в класс App.xaml.cs и переопределим метод OnStartup.
protected override void OnStartup(StartupEventArgs e)
{
       Bootstrapper bootstrapper = new Bootstrapper();
       bootstrapper.Run();
}
Когда мы проделали все эти действия, можем запустить наш проект и посмотреть, что у нас получилось.

Надеюсь, что использование загрузчика MefBootstrapper не оказалось для вас сложнее, чем использование UnityBootstrapper. Более сложные примеры основываются только на вашем умении оперировать MEF для связывания реализации с интерфейсами, не более того. В следующей статье мы постараемся рассмотреть использование класса EventAggregator.  

No comments:

Post a Comment