- PVSM.RU - https://www.pvsm.ru -
Оглавление
- Введение [1]
- Инициализация приложений Prism [2]
- Управление зависимостями между компонентами [3]
- Разработка модульных приложений [4]
- Реализация паттерна MVVM
- Продвинутые сценарии MVVM
- Создание пользовательского интерфейса
- Навигация
- Способы коммуникации между слабосвязанными компонентами
Модульное приложение является таким приложением, которое можно разделить на ряд функциональных блоков (модули), которые могут быть интегрированы в одно целое. Клиентский модуль инкапсулирует часть общей функциональности приложения и обычно представляет собой набор взаимосвязанных функциональных частей. Он может включать набор связанных компонент, таких как функции приложения, включая пользовательский интерфейс и бизнес-логику, или части инфраструктуры приложения, такие как службы уровня приложения для журналирования, или аутентификации и авторизации пользователей. Модули независимы друг от друга, но могут взаимодействовать друг с другом слабо связанным способом. Модульные приложения могут облегчить разработку, тестирование, развертывание, и расширение вашего приложения.
Например, рассмотрим персональное банковское приложение. Пользователь может получить доступ к множеству функций, таких как передача денег между учетными записями, оплата счетов, и обновление персональных данных, используя единственный пользовательский интерфейс (UI). Однако, каждая из этих функций инкапсулируется в пределах дискретного модуля. Эти модули связываются друг с другом и с системами бэкэнда, такими как серверы баз данных и веб-сервисы. Прикладные службы интегрируют различные компоненты в пределах каждого из различных модулей и обрабатывают взаимодействие с пользователем. Пользователь видит интегрированное представление, которое похоже на единственное цельное приложение.
Следующая иллюстрация показывает проект модульного приложения.
Вы, вероятно, уже создаете хорошо спроектированное приложение, используя сборки, интерфейсы, классы и хорошие объектно-ориентированные принципы разработки. В этом случае, если не принять серьёзные меры, то дизайн ваших приложения останется «монолитным» (где вся функциональность реализуется сильно связанным способом в пределах приложения), это может привести к тому, что приложение будет трудно разработать, тестировать, расширять, и поддерживать.
Модульный подход к построению приложения, с другой стороны, может помочь выделить крупномасштабные функциональные области и разрабатывать, или тестировать их независимо. Это может сделать разработку и тестирование легче, а ваше приложение более гибким и легко расширяемым в будущем. Преимущество модульного подхода в том, что он может сделать архитектуру приложения более гибкой и удобной для сопровождения, путём разделения приложения на части, которыми легко управлять. Каждая часть инкапсулирует определенную функциональность, и интегрируется через четкие, но слабо связанные каналы.
Prism помогает разрабатывать модульные приложения и управлять модулями во время выполнения. Используя функциональность Prism, вы можете сэкономить время, так как вам не нужно реализовывать и тестировать свою собственную платформу для построения модульных приложений. Prism поддерживает следующие функции для разработки модульных приложений:
Заметка.
При использовании этого подхода, сборки с модулями могут не компилироваться автоматически, при сборке проекта, и не копироваться в выходную директорию, так как на них отсутствуют ссылки из проекта исполняемого файла.
Этот раздел предоставляет базовые понятия, связанные с модульным принципом в Prism, включая интерфейс IModule, процесс загрузки модулей, каталог модулей, поддержание связи между модулями, а также контейнеры внедрения зависимостей.
Модуль является логическим набором функциональных компонент и ресурсов, собранных в одном месте. Модули могут быть разработаны, протестированы, развернуты, и интегрированы в приложение по отдельности. Пакет может включать одну или более сборок, которые могут располагаться по отдельности, или собираться в едином XAP файле. У каждого модуля имеется центральный класс, который ответственен за инициализацию и интеграцию функциональности модуля в приложение. Этот класс реализует интерфейс IModule. Присутствия класса, реализующего интерфейс IModule, достаточно, чтобы идентифицировать пакет как модуль. У интерфейса IModule есть единственный метод Initialize, в пределах которого можно реализовать любую логику, необходимую для инициализации и интеграции функциональности модуля в приложение. В зависимости от цели модуля, он может регистрировать представления в регионах пользовательского интерфейса, делать доступными для приложения дополнительные службы, или расширять его функциональность. Следующий код показывает минимальную реализацию модуля.
public class MyModule : IModule
{
public void Initialize()
{
// Код инициализации расположен здесь.
}
}
Заметка
Вместо использования механизма инициализации, предоставляемого интерфейсом IModule, Stock Trader RI использует декларативный, основанный на атрибутах подход для того, чтобы зарегистрировать представления, службы, и типы.
Процесс загрузки модуля в Prism включает следующее:
Следующая иллюстрация показывает процесс загрузки модуля.
ModuleCatalog содержит информацию о модулях, которые могут использоваться приложением. Каталог является, по существу, набором классов ModuleInfo. Каждый модуль описывается классом ModuleInfo, который хранит имя, тип и расположение модуля. Есть несколько типичных подходов к заполнению ModuleCatalog экземплярами ModuleInfo:
Выбор механизма регистрации и обнаружения, который следует использовать, зависит от того, что необходимо приложению. Использование конфигурационного файла или файла XAML позволяет приложению не хранить ссылки на модули. Использование локального каталога может позволить приложению обнаруживать модули без необходимости определять их в каком-либо файле.
Приложения Prism могут инициализировать модули при первой возможности, что известно как «when available», или когда приложение нуждается в них, что известно как «on-demand». Для приложений Silverlight, модули могут быть загружены вместе с приложением, или в фоновом режиме после того, как приложение запустится. Рассмотрите следующие инструкции по загрузке модулей:
Рассмотрите способ разделения приложения, сценарии общего использования, скорость запуска приложения, а также число и размер загрузок, чтобы определить то, как необходимо сконфигурировать загрузку и инициализацию модулей.
Prism предоставляет следующие классы, необходимые для загрузки вашего приложения: UnityBootstrapper и MefBootstrapper. Эти классы могут использоваться для создания и конфигурирования менеджера модулей, который необходим для обнаружения и загрузки модулей. Можно переопределить метод конфигурации каталога модулей, чтобы зарегистрировать модули, определенные в файле XAML, конфигурационном файле, или указать расположение каталога.
Используйте метод Initialize модуля, чтобы интегрировать модуль с остальной частью приложения. Способ, которым вы это сделаете, будет зависеть от структуры приложения и содержимого модуля. Ниже показаны общие шаги, которые необходимо совершить для интеграции модуля в ваше приложение:
Даже учитывая, что модули должны быть слабо связаны, им свойственно обмениваться друг с другом данными и сообщениями. Есть несколько коммуникационных паттернов для слабо связанных систем, каждый со своими преимуществами и недостатками. Как правило, в конечном счёте, используются комбинации этих паттернов. Далее представлены некоторые из этих паттернов:
Контейнеры, такие как Unity Application Block (Unity) и Managed Extensibility Framework (MEF), позволяют вам легко использовать инверсию управления (IoC) и внедрение зависимостей, которые являются мощными шаблонами разработки, помогающими соединять компоненты слабо связанным способом. Это позволяет компонентам получать ссылки на другие компоненты, от которых они зависят, без необходимости жёстко кодировать эти ссылки, получая, таким образом, повторное использование кода и повышенную гибкость. Внедрение зависимости очень полезно при создании слабо связанного модульного приложения. Prism разрабатывалась так, чтобы быть независимым от DI контейнера, используемого для композиции компонентов приложения. Выбор контейнера – ваше дело, и оно будет в значительной степени зависеть от основных эксплуатационных характеристик и предпочтений. Существуют две основные платформы внедрения зависимости для рассмотрения от Microsoft – Unity и MEF.
Patterns&practices Unity Application Block представляет полнофункциональный контейнер внедрения зависимостей. Он поддерживает инжекцию, основанную на свойствах, инжекцию, основанную на конструкторах и инжекцию политик, которая позволяет прозрачно вводить поведения и политики между компонентами. Он также поддерживает множество других функций, типичных для контейнеров внедрения зависимостей.
MEF (который теперь является частью .NET Framework 4 и Silverlight 4) позволяет создавать расширяемые приложения, поддерживая основанную на внедрении зависимостей композицию, а также предоставляет другие функции, которые поддерживают модульную разработку приложений. Это позволяет приложению обнаруживать компоненты во время выполнения и затем интегрировать их в приложение. MEF предоставляет прекрасные возможности для расширения и композиции. Они включают обнаружение сборок и типов, разрешение зависимостей, внедрение зависимостей, и некоторые возможности по загрузке сборок и XAP файлов. Prism поддерживает использование таких функций MEF, как:
Первое решение, которое вы должны принять, состоит в том, хотите ли вы разработать модульное приложение. Есть многочисленные преимущества создания модульных приложений, как было обсуждено в предыдущем разделе, но также существует дополнительное время и усилия, которое вы должны приложить, чтобы получить эти преимущества. Если вы всё же решите разрабатывать модульное приложение, то есть ещё несколько вещей для рассмотрения:
Следующие разделы детально рассматривают эти решения.
Когда вы разрабатываете своё приложение модульным способом, вы структурируете его на отдельные клиентские модули, которые могут быть индивидуально разработаны, протестированы, и развёрнуты. Каждый модуль будет инкапсулировать часть полной функциональности вашего приложения. Одно из первых проектных решений, которые необходимо сделать, состоит в решении того, как разделить функциональность вашего приложения по дискретным модулям.
Модуль должен инкапсулировать ряд связанной функциональности и иметь набор различных обязанностей. Модуль может представить вертикальный разрез приложения или горизонтального слой служб. У больших приложений, вероятно, будут оба типа модулей.
Большое приложение может иметь модули, организованные как по вертикальным срезам, так и по горизонтальным слоям. Например, модули могут включать следующее:
У модуля должен быть минимальный набор зависимостей от других модулей. Когда у модуля есть зависимость от другого модуля, он должен быть слабо связан с ним через интерфейсы, определённые в совместно используемой библиотеке, а не через конкретные типы, или, используя EventAggregator, чтобы связаться с другими модулями через события.
Цель модульного принципа состоит в том, чтобы разделить приложение таким способом, что оно станет гибким, удобным в сопровождении, и устойчивым, даже тогда, когда функции и технологии добавляются и удаляются. Лучший способ достичь этого состоит в том, чтобы разработать ваше приложение так, чтобы модули были настолько независимы, насколько возможно, имели чётко определённые интерфейсы, и были максимально изолированы.
Есть несколько способов создания и упаковки модули. Рекомендуемый и наиболее распространённый способ заключается в создании единственной сборки на модуль. Это помогает разделить модули логически и способствует надлежащей инкапсуляции. Это также позволяет говорить о сборке как о модуле и наоборот. Однако ничто не препятствует тому, чтобы единственная сборка содержала несколько модулей, в некоторых случаях это может быть предпочтительным для минимизации числа проектов в вашем решении. Большое приложение вполне может иметь 10-50 модулей. Перенос каждого модуля в свой собственный проект добавляет сложности к решению и может замедлить производительность Visual Studio. Иногда имеет смысл разделить модуль или набор модулей на собственные решения, если вы хотите придерживаться одного модуля на проект или сборку.
Для приложений Silverlight модули обычно упаковываются в отдельных файлах XAP, хотя в некоторых случаях, у вас может быть больше чем один модуль на XAP. Следует рассмотреть, в каком количестве файлов XAP вы нуждаетесь, чтобы минимизировать число и размер запросов загрузки, требуемых для запуска приложения и активации новой опции. Если вы хотите разделять каждый модуль на его собственный проект/сборку, вы должны решить, вставить ли каждую сборку его собственный XAP для развёртывания или включать несколько сборок в единственный XAP.
Некоторые факторы, влияющие на ваш выбор того, включать ли несколько модулей в единственный файл XAP или разделить их:
Чтобы избежать загружать одной и те же сборки несколько раз в каждом XAP, есть два подхода, которые могут использоваться:
Модуль может зависеть от компонентов и услуг, предоставленных хост-приложением или другими модулями. Prism поддерживает возможность регистрации зависимости между модулями так, чтобы они были загружены и инициализированы в правильном порядке. Prism также поддерживает инициализацию модулей, после их загрузки. Во время инициализации, модуль может получить ссылки на дополнительные компоненты и службы, которых ему требуются, и/или зарегистрировать любые компоненты и службы, которые он содержит, чтобы сделать их доступными для других модулей.
Модуль должен использовать независимый механизм для получения экземпляров внешних интерфейсов вместо того, чтобы непосредственно создавать конкретные типы. Он может сделать это через контейнер внедрения зависимостей или фабричные службы. Контейнеры внедрения зависимости, такие как Unity или MEF, позволяют типу автоматически получать экземпляры интерфейсов посредством внедрения зависимостей. Prism интегрируется и с Unity, и с MEF, чтобы позволить модулям легко использовать механизм внедрения зависимостей.
Следующая схема показывает типичную последовательность операций при загрузке модулей, которые требуются для получения или регистрации ссылок на компоненты и службы.
В этом примере сборка OrdersModule определяет класс OrdersRepository (наряду с другими представлениями и классами, которые реализуют функциональность заказа). Сборка CustomerModule определяет класс CustomersViewModel, который зависит от класса OrdersRepository, основанного на интерфейсе, предоставленном службой. Запуск приложения и процесс загрузки содержит следующие шаги:
Заметка
Интерфейс, используемый, чтобы представить OrderRespository (IOrderRepository), мог находиться в отдельной сборке совместно используемых служб, или в сборке служб заказов, которая содержит только интерфейсы служб и типы, необходимые для их публикации. Таким образом, нет никакой жёсткой зависимости между CustomersModule и OrdersModule.
Заметьте, что у обоих модулей есть неявная зависимость от контейнера внедрения зависимости. Эта зависимость вводится во время создания модуля в загрузчике.
Этот раздел описывает общие сценарии, с которыми вы столкнётесь, работая с модулями в вашем приложении. Эти сценарии включают определение модуля, регистрацию и обнаружение модулей, загрузку модулей, инициализацию модулей, определение зависимостей модуля, загрузку модулей по требованию, загрузку удалённых модулей в фоновом режиме, и определение состояния процесса загрузки. Можно зарегистрировать и модули в коде, в XAML, в конфигурационном файле приложения, или сканируя локальный каталог.
Модуль является логическим набором функциональности и ресурсов, который может быть отдельно разработан, протестирован, развернут, и интегрирован в приложение. У каждого модуля есть центральный класс, который ответственен за инициализацию модуля и интеграции его функциональности в приложение. Этот класс реализует интерфейс IModule, как показано ниже.
public class MyModule : IModule
{
public void Initialize()
{
// Инициализация модуля.
}
}
Заметка
Имена модулей должны быть уникальными в пределах всего приложения.
Тот путь, которым вы реализуете метод Initialize, будет зависеть от требований вашего приложения. Тип класса модуля, режим инициализации, и любые зависимости модуля задаются в каталоге модулей. Для каждого модуля в каталоге загрузчик создаёт экземпляр класса модуля и затем вызывает метод Initialize. Модули обрабатываются в порядке, определённом в каталоге модулей. Порядок инициализации во время выполнения зависит от того, когда модули загрузятся, станут доступны, и их зависимости удовлетворятся.
В зависимости от типа каталога модулей, который использует ваше приложение, зависимости модуля, могут быть установлены или декларативными атрибутами непосредственно в классе модуля, или в пределах файла каталога модулей. Следующие разделы предоставят более детальную информацию.
Модули, которые может загрузить приложение, определяются в каталоге модулей. Загрузчик модулей Prism использует каталог модулей, чтобы определить, какие модули доступны для загрузки, когда их загрузить, и в каком порядке они должны быть загружены.
Каталог модулей представляет собой класс, реализующий интерфейс IModuleCatalog. Класс каталога модулей создаётся классом загрузчика во время инициализации приложения. Prism предоставляет различные реализации каталога модулей, из которых вы можете выбрать необходимый. Можно также заполнить каталог модулей из другого источника данных, вызывая метод AddModule или наследуя класс от ModuleCatalog, чтобы создать каталог модулей со специализированным поведением.
Заметка
Как правило, модули в Prism используют контейнер внедрения зависимости и Common Service Locator, чтобы получить экземпляры типов, которые требуются для инициализации модуля. Хотя полный процесс регистрации, обнаружения, загрузки, и инициализации модулей является одним и тем же, детали могут измениться в зависимости от того, какой контейнер используется. Контейнерно-специфичные различия между подходами рассматриваются всюду в этой теме.
Самый базовый каталог модулей предоставляется классом ModuleCatalog. Можно использовать этот каталог, чтобы зарегистрировать модули в коде, задавая тип класса модуля. Можно также задать режим инициализации и имя модуля. Чтобы зарегистрировать модуль непосредственно в классе ModuleCatalog, вызовите метод AddModule в классе Bootstrapper своего приложения.
protected override void ConfigureModuleCatalog()
{
Type moduleCType = typeof(ModuleC);
ModuleCatalog.AddModule(
new ModuleInfo()
{
ModuleName = moduleCType.Name,
ModuleType = moduleCType.AssemblyQualifiedName,
});
}
В предыдущем примере, на модули непосредственно ссылается оболочка, таким образом, определяются типы классов модулей, которые могут использоваться в вызове метода AddModule. Именно поэтому этот пример использует typeof(Module), чтобы добавить модули к каталогу.
Заметка
Если у приложения есть прямая ссылка на тип модуля, можно добавить этот тип, как показано выше. Иначе вы должны предоставить полностью определённое имя типа и расположение сборки.
Чтобы увидеть другой пример определения каталога модулей в коде, смотрите StockTraderRIBootstrapper.cs в Stock Trader RI.
Заметка
Базовый класс Bootstrapper предоставляет метод CreateModuleCatalog, чтобы помочь в создании ModuleCatalog. По умолчанию, этот метод создаёт экземпляр ModuleCatalog, но этот метод может быть переопределён в производном классе для создания других типов каталога модулей.
Можно определить каталог модулей декларативно в файле XAML. Файл XAML определяет, какой нужно создать класс каталога модулей и какие модули к нему добавить. Обычно .xaml файл добавляется как ресурс к вашему проекту оболочки. Каталог модулей создаётся в загрузчике вызовом метода CreateFromXaml. С технической точки зрения, этот подход подобен определению ModuleCatalog в коде, потому что файл XAML просто определяет иерархию объектов, которые будут инстанцироваться.
Следующий пример кода показывает файл XAML, задающий каталог модулей.
<Modularity:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:Modularity="clr-namespace:Microsoft.Practices.Prism.Modularity;assembly=Microsoft.Practices.Prism">
<Modularity:ModuleInfoGroup Ref="ModuleB.xap" InitializationMode="WhenAvailable">
<Modularity:ModuleInfo ModuleName="ModuleB" ModuleType="ModuleB.ModuleB, ModuleB, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</Modularity:ModuleInfoGroup>
<Modularity:ModuleInfoGroup InitializationMode="OnDemand">
<Modularity:ModuleInfo Ref="ModuleE.xap" ModuleName="ModuleE" ModuleType="ModuleE.ModuleE, ModuleE, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
<Modularity:ModuleInfo Ref="ModuleF.xap" ModuleName="ModuleF" ModuleType="ModuleF.ModuleF, ModuleF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" >
<Modularity:ModuleInfo.DependsOn>
<sys:String>ModuleE</sys:String>
</Modularity:ModuleInfo.DependsOn>
</Modularity:ModuleInfo>
</Modularity:ModuleInfoGroup>
<!-- Информация о модуле вне группы -->
<Modularity:ModuleInfo Ref="ModuleD.xap" ModuleName="ModuleD" ModuleType="ModuleD.ModuleD, ModuleD, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</Modularity:ModuleCatalog>
Заметка
ModuleInfoGroups обеспечивают удобный способ сгруппировать модули, которые находятся в одном и том же .xap файле или сборке, инициализируются одинаковым образом, или имеют зависимости только от модулей в той же самой группе. Зависимости между модулями могут быть определены в пределах модулей, в ModuleInfoGroup, однако, невозможно задать зависимости между модулями в различных ModuleInfoGroups. Помещение модулей в группы модулей является не обязательным. Свойства, которые устанавливаются для группы, будут применены ко всем модулям в ней. Отметьте, что модули также могут быть зарегистрированы, не будучи в группе.
В классе Bootstrapper вы должны указать, что файл XAML является источником для ModuleCatalog, как показано ниже.
protected override IModuleCatalog CreateModuleCatalog()
{
return ModuleCatalog.CreateFromXaml(new Uri("/MyProject.Silverlight;component/ModulesCatalog.xaml",
UriKind.Relative));
}
В WPF возможно задать информацию о модуле в файле App.config. Преимущество этого подхода состоит в том, что этот файл не компилируется в приложение. Это облегчает добавление или удаление модулей во время выполнения, без перекомпиляции приложения.
Следующий код показывает конфигурационный файл, задающий каталог модулей. Если вы хотите, чтобы модуль автоматически загрузился, установите startupLoaded=«true».
<modules>
<module assemblyFile="ModularityWithUnity.Desktop.ModuleE.dll" moduleType="ModularityWithUnity.Desktop.ModuleE, ModularityWithUnity.Desktop.ModuleE, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ModuleE" startupLoaded="false" />
<module assemblyFile="ModularityWithUnity.Desktop.ModuleF.dll" moduleType="ModularityWithUnity.Desktop.ModuleF, ModularityWithUnity.Desktop.ModuleF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" moduleName="ModuleF" startupLoaded="false">
<dependencies>
<dependency moduleName="ModuleE"/>
</dependencies>
</module>
</modules>
Заметка
Даже если ваши сборки находятся в глобальном кэше сборок или в той же самой папке, где и приложение, требуется атрибут assemblyFile. Атрибут используется, чтобы отобразить moduleType на корректный IModuleTypeLoader, который будет использоваться.
В классе Bootstrapper приложения, вы должны задать, что конфигурационный файл является источником для ModuleCatalog. Чтобы сделать это, используйте класс ConfigurationModuleCatalog, как показано в следующем коде.
protected override IModuleCatalog CreateModuleCatalog()
{
return new ConfigurationModuleCatalog();
}
Заметка
Можно все ещё добавить модули к ConfigurationModuleCatalog в коде. Можно использовать это, например для того, чтобы удостовериться в том, что модули абсолютно необходимые для функционирования вашего приложения, добавляются в каталог модулей.
Заметка
Silverlight не поддерживает конфигурационные файлы. Если вы хотите использовать такой подход для конфигурации в Silverlight, рекомендуется создать ваш собственный ModuleCatalog, который читает конфигурацию модулей из веб-сервиса на сервере.
Класс DirectoryModuleCatalog позволяет вам задавать локальный каталог как каталог модулей в WPF. Этот каталог модулей будет сканировать указанную папку, и искать сборки, которые предоставляют модули для вашего приложения. Чтобы использовать этот подход, вы должны будете использовать декларативные атрибуты на своих классах модулей, чтобы определить имена модулей и любые зависимости, которые они имеют. Следующий пример кода показывает каталог модулей, который заполняется, обнаруживая сборки в локальном каталоге.
protected override IModuleCatalog CreateModuleCatalog()
{
return new DirectoryModuleCatalog() {ModulePath = @".Modules"};
}
Заметка
Эта функциональность не поддерживается в Silverlight, потому что модель безопасности Silverlight не позволяет вам загружать сборки из файловой системы.
После того, как ModuleCatalog заполнится, модули готовы для загрузки и инициализации. Загрузка модуля означает, что сборка модуля загружается с диска в память. Если сборка отсутствует на диске, то её, возможно, придётся сначала получить из другого источника. Примером этого является загрузка сборки из интернета, используя Silverlight .xap файлы. ModuleManager ответственен за координирование процесса инициализации и загрузки.
После загрузки модулей, они инициализируются. Это означает, что создаётся экземпляр класса модуля и вызывается его метод Initialize. Инициализация является тем местом, где происходит интеграция модуля в приложение. Рассмотрим следующие возможности при инициализации модуля:
Заметка
Время жизни экземпляра модуля по умолчанию является недолгим. После того, как вызывают метод Initialize во время процесса загрузки, ссылка на экземпляр модуля удаляется. Если вы не создадите цепочку ссылок на экземпляр модуля, то он будет собран сборщиком мусора. Такое поведение может быть нежелательным при отладке, если вы подписываетесь на события, которые содержат слабую ссылку на ваш модуль, потому что ваш модуль «исчезает» при запуске сборщика мусора.
Модули могут зависеть от других модулей. Если Module A зависит от Module B, то Module B должен быть инициализирован перед Module A. ModuleManager отслеживает эти зависимости и инициализирует модули в правильном порядке. В зависимости от того, как вы определили свой каталог модулей, можно определить свои зависимости модуля в коде, конфигурации, или XAML.
Для приложений WPF, которые регистрируют модули в коде или обнаруживают их в папке, Prism предоставляет декларативные атрибуты для класса модуля, как показано в следующем примере.
[Module(ModuleName = "ModuleA")]
[ModuleDependency("ModuleD")]
public class ModuleA: IModule
{
...
}
Следующий XAML показывает, что Module F зависит от Module E.
<Modularity:ModuleInfo Ref="ModuleF.xap" ModuleName="ModuleF" ModuleType="ModuleF.ModuleF, ModuleF, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" >
<Modularity:ModuleInfo.DependsOn>
<sys:String>ModuleE</sys:String>
</Modularity:ModuleInfo.DependsOn>
</Modularity:ModuleInfo>
Следующий пример файла App.config показывает, что Module D зависит от Module B.
<modules>
<module assemblyFile="Modules/ModuleD.dll" moduleType="ModuleD.ModuleD, ModuleD" moduleName="ModuleD">
<dependencies>
<dependency moduleName="ModuleB"/>
</dependencies>
</module>
Для загрузки модулей по требованию, вы должны указать, что они должны быть загружены в каталог модулей с установленным параметром InitializationMode в OnDemand. После этого, вы должны добавить код в своё приложении, запрашивающий загрузку модуля.
Определение, того что модуль должен загружаться по требованию через атрибуты, показано в следующем примере.
protected override void ConfigureModuleCatalog()
{
Type moduleCType = typeof(ModuleC);
this.ModuleCatalog.AddModule(new ModuleInfo()
{
ModuleName = moduleCType.Name,
ModuleType = moduleCType.AssemblyQualifiedName,
InitializationMode = InitializationMode.OnDemand
});
}
Можно определить InitializationMode.OnDemand, когда вы задаёте каталог модулей в XAML, как показано в следующем примере кода.
...
<Modularity:ModuleInfoGroup InitializationMode="OnDemand">
<Modularity:ModuleInfo Ref="ModuleE.xap" ModuleName="ModuleE" ModuleType="ModuleE.ModuleE, ModuleE, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
...
Вы можете определить InitializationMode.OnDemand при задании каталога модулей в файле App.config, как показано в следующем примере кода.
...
<module assemblyFile="Modules/ModuleC.dll" moduleType="ModuleC.ModuleC, ModuleC" moduleName="ModuleC" startupLoaded="false"/>
....
После того, как модуль определяется как загружаемый по требованию, приложение может запросить его загрузку. Коду, который хочет инициировать загрузку, требуется получить ссылку на службу IModuleManager, зарегистрированную в контейнере загрузчиком.
private void OnLoadModuleCClick(object sender, RoutedEventArgs e)
{
moduleManager.LoadModule("ModuleC");
}
Загрузка модулей в фоновом режиме после того, как приложение запускается, или только когда пользователь в них нуждается, может уменьшить время запуска приложения.
В приложениях Silverlight модули упаковываются в .xap файлы. Чтобы загрузить модуль отдельно от приложения, создайте отдельный .xap файл. Можно захотеть поместить несколько модулей в единственный .xap файл, чтобы оптимизировать число запросов загрузки в замен размера каждого .xap файла.
Заметка
Для каждого .xap файла, вы должны будете создать новый проект приложения Silverlight. В Visual Studio 2008 и 2010, только проекты приложения производят отдельные .xap файлы. Вы не будете нуждаться в App.xaml или файлах MainPage.xaml в этих проектах.
Класс ModuleManager предоставляет событие для отслеживания продвижения загрузки модулей. Он предоставляет загруженные байты против полного размера загрузки для получения процента продвижения. Можно использовать его, чтобы вывести на экран визуальные индикаторы хода загрузки.
this.moduleManager.ModuleDownloadProgressChanged += this.ModuleManager_ModuleDownloadProgressChanged;
void ModuleManager_ModuleDownloadProgressChanged(object sender,
ModuleDownloadProgressChangedEventArgs e)
{
...
}
Служба ModuleManager предоставляет событие, чтобы отследить, когда модуль загружен или не в состоянии загрузиться.
this.moduleManager.LoadModuleCompleted += this.ModuleManager_LoadModuleCompleted;
void ModuleManager_LoadModuleCompleted(object sender, LoadModuleCompletedEventArgs e)
{
...
}
Чтобы сохранить приложение и модули слабо связанным, вы должны избегать использования этого события для интеграции модуля с приложением. Используйте вместо этого метод модуля Initialize.
LoadModuleCompletedEventArgs содержит свойство IsErrorHandled. Если модуль не в состоянии загрузиться, и приложение хочет препятствовать тому, чтобы ModuleManager регистрировал ошибку и выдал исключение, нужно установить это свойство в true.
Заметка
После того, как модуль загружается и инициализируется, сборка модуля не может быть выгружена. Ссылка экземпляра модуля не будет сохранена библиотеками Prism, таким образом, экземпляр класса модуля сможет быть собран «мусор» после того, как инициализация выполнена.
Этот раздел только выделяет различия при использовании MEF в качестве контейнера внедрения зависимостей.
Заметка
При использовании MEF, MefBootstrapper использует MefModuleManager. Он расширяет ModuleManager и реализует интерфейс IPartImportsSatisfiedNotification, чтобы гарантировать, что ModuleCatalog обновляется, когда новые типы импортируются MEF.
При использовании MEF можно применять атрибут ModuleExport к классам модуля, чтобы MEF мог их автоматически обнаружить.
[ModuleExport(typeof(ModuleB))]
public class ModuleB : IModule
{
...
}
Можно также использовать MEF, чтобы обнаружить и загрузить модули, используя класс AssemblyCatalog, который может использоваться для обнаружения всех экспортируемых классов модуля в сборке, и класс AggregateCatalog, который позволяет нескольким каталогам быть объединёнными в один логический каталог. По умолчанию, класс MefBootstrapper создает экземпляр AggregateCatalog. Можно переопределить метод ConfigureAggregateCatalog, чтобы зарегистрировать сборки.
protected override void ConfigureAggregateCatalog()
{
base.ConfigureAggregateCatalog();
//На Module A определены ссылки в проекте и непосредственно в коде.
this.AggregateCatalog.Catalogs.Add(
new AssemblyCatalog(typeof(ModuleA).Assembly));
this.AggregateCatalog.Catalogs.Add(
new AssemblyCatalog(typeof(ModuleC).Assembly));
}
Prism реализация MefModuleManager синхронизирует AggregateCatalog MEF и Prism ModuleCatalog, таким образом позволяя Prism обнаружить модули, добавленные через ModuleCatalog или AggregateCatalog.
Заметка
MEF широко использует Lazy<T>, чтобы предотвратить инстанцирование экспортируемых и импортированных типов до использования свойства Value.
MEF предоставляет класс DirectoryCatalog, который может использоваться, чтобы просмотреть папку для сборок, содержащих модули (и другие экспортируемые MEF типы). В этом случае, вы переопределяете метод ConfigureAggregateCatalog, чтобы зарегистрировать каталог. Этот подход доступен только в WPF.
Чтобы использовать этот подход, вы сначала должны задать имена модулей и их зависимости, используя атрибут ModuleExport, как показано в следующем примере.
protected override void ConfigureAggregateCatalog()
{
base.ConfigureAggregateCatalog();
DirectoryCatalog catalog = new DirectoryCatalog("DirectoryModules");
this.AggregateCatalog.Catalogs.Add(catalog);
}
Для приложений WPF, используйте атрибут ModuleExport, как показано ниже.
[ModuleExport(typeof(ModuleA), DependsOnModuleNames = new string[] { "ModuleD" })]
public class ModuleA : IModule
{
...
}
Поскольку MEF позволяет вам обнаруживать модули во время выполнения, можно также во время выполнения обнаружить новые зависимости между модулями. Хотя можно использовать MEF вместе с ModuleCatalog, важно помнить, что ModuleCatalog проверяет цепочки зависимости, при загрузке из XAML или конфигурации (прежде чем какие-либо модули будут загружены). Если модуль будет перечислен в ModuleCatalog и затем будет загружен с использованием MEF, то будут использоваться зависимости ModuleCatalog, и атрибут DependsOnModuleNames будет проигнорирован. Использование MEF с ModuleCatalog наиболее распространено в приложениях Silverlight, у которых есть модули в отдельных файлах XAP.
Если вы используете MEF и атрибут ModuleExport для того, чтобы определить модули и зависимости от модуля, можно использовать свойство InitializationMode, чтобы определить, что модуль должен быть загружен по требованию, как показано ниже.
[ModuleExport(typeof(ModuleC), InitializationMode = InitializationMode.OnDemand)]
public class ModuleC : IModule
{
}
Под капотом, приложение Prism, использующее MEF, используют класс MEF DeploymentCatalog, чтобы загрузить .xap файлы и обнаружить сборки и типы в пределах этих .xap файлов. MefXapModuleTypeLoader добавляет каждый DeploymentCatalog к AggregateCatalog.
Если два различных .xap файла добавляются и содержат одну и ту же совместно используемую сборку, то эти типы импортируются снова. Это может вызвать ошибки рекомпозиции, когда тип назначен быть синглтоном и находится в сборке, совместно используемой модулями. Microsoft.Practices.Prism.MefExtensions.dll пример такой сборки.
Чтобы избежать двойного импорта, откройте каждый проект модуля и отметьте те совместно используемые DLL как 'Copy Local'=false. Это помешает сборке быть упакованной в .xap файле модуля и быть импортированной снова. Это также уменьшает полный размер каждого .xap файла. Вы должны гарантировать, что или приложение ссылается на совместно используемую сборку, или то, что будет загружен .xap файл, который содержит совместно используемые сборки прежде, чем .xap файлы модулей будет загружены.
Для получения дополнительной информации о кэшировании сборок, смотрите «How to: Use Assembly Library Caching» на MSDN: http://msdn.microsoft.com/en-us/library/dd833069(VS.95).aspx [5]
Чтобы узнать больше о модульном принципе в Prism, смотрите Modularity with MEF for WPF QuickStart или Modularity with Unity for WPF QuickStart. Чтобы узнать больше о QuickStarts, смотрите Modularity QuickStarts for WPF [6].
Для получения информации о функциях библиотеки Prism, используемых при построении модульных приложений, смотрите "Modules [7]" в "Extending Prism [8]."
Автор: Unrul
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/interfejsy/32646
Ссылки в тексте:
[1] Введение: http://habrahabr.ru/post/176851/
[2] Инициализация приложений Prism: http://habrahabr.ru/post/176853/
[3] Управление зависимостями между компонентами: http://habrahabr.ru/post/176861/
[4] Разработка модульных приложений: http://habrahabr.ru/post/176863/
[5] http://msdn.microsoft.com/en-us/library/dd833069(VS.95).aspx: http://msdn.microsoft.com/en-us/library/dd833069(VS.95).aspx
[6] Modularity QuickStarts for WPF: http://msdn.microsoft.com/en-us/library/ff921068(v=PandP.40).aspx
[7] Modules: http://msdn.microsoft.com/en-us/library/gg430866(v=PandP.40).aspx#Modules
[8] Extending Prism: http://msdn.microsoft.com/en-us/library/gg430866(v=PandP.40).aspx
Нажмите здесь для печати.