Миграция Silverlight приложений с Prism 2.2 на Prism 4 MEF edition

    Подходит время, когда будет объявлено об окончании разработки библиотеки Prism 4, предназначенной для создания модульных и гибких Silverlight и WPF приложений. Новая версия имеет большое число изменений, улучшений и нововведений. В качестве одного из главных нововведений можно отметить добавление поддержки MEF в качестве контейнера (в предыдущей версии поддерживался только Unity контейнер).

    В этой статье я хотел бы затронуть вопрос миграции с Prism 2.2 на Prism 4 c учётом перехода на использование MEF контейнера вместо Unity.

    Изменения в именовании и составе сборок


    Командой разработчиков было принято решение изменить названия сборок и имена namespace:
    • Microsoft.Practices.Composite изменяется на Microsoft.Practices.Prism;
    • Сборка Microsoft.Practices.Composite.Presentation становится частью сборки Microsoft.Practices.Prism;
    • Namespace Microsoft.Practices.Composite.Presentation заменяется на Microsoft.Practices.Prism;
    • Microsoft.Practices.Composite.UnityExtensions изменяется на Microsoft.Practices.Prism.UnityExtensions;
    • Добавляется сборка Microsoft.Practices.Prism.MefExtensions и соответствующий namespace;
    • Добавляется сборка Microsoft.Practices.Prism.Interactivity;

    Изменение списка сборок


    При миграции следует произвести следующие изменения в списке сборок каждого проекта:
    • - Microsoft.Practices.Unity.dll
    • - Microsoft.Practices.Composite.dll
    • - Microsoft.Practices.Composite.Presentation.dll (если присутствует)
    • + Microsoft.Practices.Prism.dll
    • + System.ComponentModel.Composition.dll
    • + System.ComponentModel.Composition.Initialization.dll, если в проекте будет использоваться один из классов этой библиотеки
    • + Microsoft.Practices.Prism.MefExtensions.dll, если проект содержит модуль

    Заменив сборки, следует изменить namespace. Это можно осуществить с помощью стандартного диалога Find and Replace (Ctrl-H), введя Microsoft.Practices.Composition и Microsoft.Practices.Prism в соответствующие поля. Учитывая, что сборка Microsoft.Practices.Composite.Presentation.dll более отдельно не существует, то после предыдущей замены следует заменить Microsoft.Practices.Prism.Presentation на Microsoft.Practices.Prism

    Примечание:
    Не следует забывать изменять имена namespace не только в .cs файлах, но и в .xaml, и в .config (если требуется).

    Замена Unity контейнера


    MEF контейнер имеет определённые достоинства и недостатки. Их следует знать, прежде чем принимать решение о миграции на MEF контейнер.

    Недостатки:
    • Не может инстанцировать класс без регистрации в контейнере
    • Не может работать с открытыми generic классами

    Преимущества:
    • Может импортировать части (parts) из папок со сборками
    • Встроенная поддержка XAP файлов
    • Поддержка рекомпозиции
    • Поддержка наследования атрибута [Export]
    • Библиотека является составной частью .Net framework и входит в клиентскую часть фреймворка, что означает, что сборки этой библиотеки не будут включаться в XAP файл.


    Избавление от Unity контейнера следует начать с удаления всех вхождений IUnityContainer в качестве конструктора классов или публичного свойства. Если в конструктор передаются другие параметры из контейнера (constructor injection), то их оставляем, а конструктор помечаем атрибутом [ImportingConstructor].

    Пример:
    //Prism 2.2<br>public class SampleClass<br>{<br>  private readonly IRegionManager regionManager;<br>  private readonly IUnityContainer container;<br><br>  public SampleClass(IUnityContainer container, IRegionManager regionManager)<br>  {<br>    this.container = container;<br>    this.regionManager = regionManager;<br>  }<br>}<br><br>//Prism 4<br>[Export(typeof(SampleClass))]<br>public class SampleClass<br>{<br>  private readonly IRegionManager regionManager;<br><br>  [ImportingConstructor]<br>  public SampleClass(IRegionManager regionManager)<br>  {<br>    this.regionManager = regionManager;<br>  }<br>}<br><br>* This source code was highlighted with Source Code Highlighter.


    Далее, все классы, которые так или иначе создавались через контейнер, должны быть помечены атрибутом [Export] (можно использовать [InheritedExport] для интерфейсов), и одновременно должен быть удалён код регистрации классов в контейнере.

    Следующим шагом является изменение синтаксиса модулей. Каждый модуль должен быть помечен атрибутом [ModuleExport(typeof(<ModuleType>))] (определение этого атрибута находится в сборке Microsoft.Practices.Prism.MefExtensions.dll).

    Пример:
    //Prism 2.2<br>public class SampleModule : IModule<br>{<br>  public void Initialize()<br>  {<br>    Register();<br>  }<br><br>  public void Register()<br>  {<br>    container.RegisterType<ISampleViewModel, SampleViewModel>();<br>  }<br>}<br><br>//Prism 4<br>[ModuleExport(typeof(SampleClass))]<br>public class SampleModule : IModule<br>{<br>  public void Initialize()<br>  {<br>    ...<br>  }<br>}<br><br>[InheritedExport]<br>public interface ISampleViewModel<br>{<br>  ...<br>}<br><br>* This source code was highlighted with Source Code Highlighter.

    Следующий этап предполагает изменение Bootstarpper.cs.
    • Класс Bootstarapper следует переунаследовать от MefBootstrapper
    • Следует изменить метод CreateShell() и добавить метод InitializeShell()
    • Метод GetModuleCatalog() следует переименовать в CreateModuleCatalog()
    • Метод ConfigureAggregateCatalog() следует переопределить для того, что бы он включал все сборки, которые доступны из текущей сборки

    Пример:
    //Prism 4<br>using System.ComponentModel.Composition;<br>using System.ComponentModel.Composition.Hosting;<br><br>using Microsoft.Practices.Prism.MefExtensions;<br>using Microsoft.Practices.Prism.Regions;<br>using Modularity = Microsoft.Practices.Prism.Modularity;<br><br>namespace Sample.Shell<br>{<br>  public class Bootstrapper : MefBootstrapper<br>  {<br>    protected override DependencyObject CreateShell()<br>    {<br>      return Container.GetExportedValue<ShellView>();<br>    }<br><br>    protected override void InitializeShell()<br>    {<br>      base.InitializeShell();<br>      Application.Current.RootVisual = (ShellView)this.Shell;<br>    }<br><br>    protected override Modularity.IModuleCatalog CreateModuleCatalog()<br>    {<br>      return Modularity.ModuleCatalog.CreateFromXaml(new Uri("/Sample.Shell;component/ModulesCatalog.xaml", UriKind.Relative));<br>    }<br><br>    protected override void ConfigureAggregateCatalog()<br>    {<br>      base.ConfigureAggregateCatalog();<br>      AggregateCatalog.Catalogs.Add(new AssemblyCatalog(this.GetType().Assembly));<br>    }<br>  }<br>}<br><br>* This source code was highlighted with Source Code Highlighter.

    Ещё несколько особенностей перехода на MEF контейнер.
    • Если параметр CreationPolicy не указан ни при импорте, ни при экспорте, то класс будет использоваться как Shared (один экземпляр на все вызовы)
    • Если в контейнере регистрируется экземпляр класса или регистрация происходит с использованием ContainerControlledLifetimeManager(), то в этом случае такой класс должен быть обязательно помечен как Shared. В противном случае этот класс может быть инстанцирован несколько раз при указании атрибута [Import(RequiredCreationPolicy=CreationPolicy.NonShared)]
    • Если в соответствии с логикой программы требуется неоднократное инстанцирование класса, то это следует выполнять с использованием ExportFactory<TypeToExport>

    Пример 1:
    //Prism 2.2<br>private void RegisterTypes()<br>{<br>  SampleType sampleType = new SampleType(...);<br>  Container.RegisterInstance<ISampleType>(sampleType);<br><br>  Container.RegisterType<ISingletonSampleType, SingletonSampleType>(new ContainerControlledLifetimeManager());<br>}<br><br>//Prism 4<br>[Export]<br>[PartCreationPolicy(CreationPolicy.Shared)]<br>public class SampleType : ISampleType<br>{<br>...<br>}<br><br>[Export]<br>[PartCreationPolicy(CreationPolicy.Shared)]<br>public class SingletonSampleType : ISingletonSampleType<br>{<br>...<br>}<br><br>* This source code was highlighted with Source Code Highlighter.

    Пример 2:
    //Prism 2.2<br>private void Foo()<br>{<br>  List<ISampleType> samples = new List<ISampleType>();<br>  for(int i = 0; i == 10; i++)<br>  {<br>    ISampleType sampleTypeInstance = Container.ResolveInstance<ISampleType>();<br>    samples.Add(sampleTypeInstance);<br>  }<br>}<br><br>//Prism 4<br>[Import]<br>public ExportFactory<ISampleType> SampleTypeFactory<br>{<br>  get;<br>  set;<br>}<br><br>private void Foo()<br>{<br>  List<ISampleType> samples = new List<ISampleType>();<br>  for(int i = 0; i == 10; i++)<br>  {<br>    ISampleType sampleTypeInstance = SampleTypeFactory.CreatExport().Value;<br>    samples.Add(sampleTypeInstance);<br>  }<br>}<br><br>* This source code was highlighted with Source Code Highlighter.

    Изменения во ViewModel


    Все ViewModel (или их общего предка) следует унаследовать от класса NotificationObject. Этот класс реализует интерфейс INotifyPropertyChanged и реализует ещё один вариант метода RaisePropertyChanged(), который принимает на вход не строковое имя свойства, а само свойство. Крайне желательно отказаться от старой реализации RaisePropertyChanged() и начать использовать новую.

    Пример:
    //Prism 2.2<br>public class SampleViewModel : ISampleViewModel<br>{<br>  private SamplePropertyType sampleProperty;<br>  public SamplePropertyType SampleProperty<br>  {<br>    get<br>    {<br>      return sampleProperty;<br>    }<br>    set<br>    {<br>      sampleProperty = value;<br>      this.RaisePropertyChanged("SampleProperty");<br>    }<br>  }<br>}<br><br>//Prism 4<br>public class SampleViewModel : NotificationObject, ISampleViewModel<br>{<br>  private SamplePropertyType sampleProperty;<br>  public SamplePropertyType SampleProperty<br>  {<br>    get<br>    {<br>      return sampleProperty;<br>    }<br>    set<br>    {<br>      sampleProperty = value;<br>      this.RaisePropertyChanged(() => SampleProperty);<br>    }<br>  }<br>}<br><br>* This source code was highlighted with Source Code Highlighter.

    Подводные камни миграции


    Сборки-дубликаты

    Следует учесть, что если Prism сборки попадут в несколько XAP файлов, то при составлении композиции произойдёт генерация исключительной ситуации.

    Message: Unhandled Error in Silverlight Application Code: 4004 Category: ManagedRuntimeError Message: Microsoft.Practices.Prism.Modularity.ModuleTypeLoadingException: Failed to load type for module <ModuleType>. Error was: The composition remains unchanged. The changes were rejected because of the following error(s): The composition produced multiple composition errors, with 13 root causes. The root causes are provided below. Review the CompositionException.Errors property for more detailed information.

    1) Change in exports prevented by non-recomposable import 'Microsoft.Practices.Prism.MefExtensions.Modularity.MefModuleManager.MefXapModuleTypeLoader (ContractName=«Microsoft.Practices.Prism.MefExtensions.Modularity.MefXapModuleTypeLoader»)' on part 'Microsoft.Practices.Prism.MefExtensions.Modularity.MefModuleManager'.


    Для того, что бы избежать этой ситуации, следует выставлять всем сборкам-дубликатам свойство CopyLocal=False или воспользоваться плагином к VisualStudio2010.

    Особенности каталога модулей

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

    Пример:
    <!--ModulesCatalog.xaml, Prism 2.2--><br><Modularity:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"<br>        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"<br>        xmlns:Modularity="clr-namespace:Microsoft.Practices.Composite.Modularity; assembly=Microsoft.Practices.Composite"><br>  <Modularity:ModuleInfo <br>    Ref="SampleModule.xap" <br>    ModuleName="SampleModule" <br>    ModuleType="SampleModule.SampleModule, SampleModule, Version=1.0.0.0" /><br></Modularity:ModuleCatalog><br><br><!--ModulesCatalog.xaml, Prism 4--><br><Modularity:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"<br>        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"<br>        xmlns:Modularity="clr-namespace:Microsoft.Practices.Prism.Modularity; assembly=Microsoft.Practices.Prism"><br>  <Modularity:ModuleInfo <br>    Ref="SampleModule.xap" <br>    ModuleName="SampleModule" <br>    ModuleType="SampleModule.SampleModule, SampleModule, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" /><br></Modularity:ModuleCatalog><br><br>* This source code was highlighted with Source Code Highlighter.

    Заключение


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

    Я желаю всем успехов в этом деле и полагаю, что моя статья будет хорошим подспорьем.

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 2

      0
      Думаю тут основной вопрос в том, а стоит ли вообще переходить на новую версию? какие бенефиты?
        0
        Честно думал над вопросом сутки. Просмотрел Major Changes в каждом дропе Prism 4 и нашёл вот что:
        We having lots of internal discussions on how to best demonstrate the challenges of MVVM and MEF for modularity.

        И действительно, большая часть release notes посвящена тому, что они обновили документацию или улучшили/добавили пример.
        В этом случае можно сказать, что самое главное изменение — добавление поддержки MEF. А потому те, кто считает MEF своим путём построения модульных приложений могут переходить с Prism 2.2 на prism 4. + Определённая часть людей переходит потому, что вышла новая версия, в чём есть своё рациональное зерно.

        Ну и конечно, начинающим на MEF разрабатывать будет сложновато — у Prism докуменация гораздо детальнее и большое число примеров

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

        Самое читаемое