Pull to refresh

Руководство разработчика Prism — часть 4, разработка модульных приложений

Reading time 30 min
Views 23K
Original author: microsoft patterns & practices
Оглавление
  1. Введение
  2. Инициализация приложений Prism
  3. Управление зависимостями между компонентами
  4. Разработка модульных приложений
  5. Реализация паттерна MVVM
  6. Продвинутые сценарии MVVM
  7. Создание пользовательского интерфейса
    1. Рекомендации по разработке пользовательского интерфейса
  8. Навигация
    1. Навигация на основе представлений (View-Based Navigation)
  9. Взаимодействие между слабо связанными компонентами

Модульное приложение является таким приложением, которое можно разделить на ряд функциональных блоков (модули), которые могут быть интегрированы в одно целое. Клиентский модуль инкапсулирует часть общей функциональности приложения и обычно представляет собой набор взаимосвязанных функциональных частей. Он может включать набор связанных компонент, таких как функции приложения, включая пользовательский интерфейс и бизнес-логику, или части инфраструктуры приложения, такие как службы уровня приложения для журналирования, или аутентификации и авторизации пользователей. Модули независимы друг от друга, но могут взаимодействовать друг с другом слабо связанным способом. Модульные приложения могут облегчить разработку, тестирование, развертывание, и расширение вашего приложения.

Например, рассмотрим персональное банковское приложение. Пользователь может получить доступ к множеству функций, таких как передача денег между учетными записями, оплата счетов, и обновление персональных данных, используя единственный пользовательский интерфейс (UI). Однако, каждая из этих функций инкапсулируется в пределах дискретного модуля. Эти модули связываются друг с другом и с системами бэкэнда, такими как серверы баз данных и веб-сервисы. Прикладные службы интегрируют различные компоненты в пределах каждого из различных модулей и обрабатывают взаимодействие с пользователем. Пользователь видит интегрированное представление, которое похоже на единственное цельное приложение.

Следующая иллюстрация показывает проект модульного приложения.

Модульное приложение

Преимущества, получаемые от создания модульных приложений


Вы, вероятно, уже создаете хорошо спроектированное приложение, используя сборки, интерфейсы, классы и хорошие объектно-ориентированные принципы разработки. В этом случае, если не принять серьёзные меры, то дизайн ваших приложения останется «монолитным» (где вся функциональность реализуется сильно связанным способом в пределах приложения), это может привести к тому, что приложение будет трудно разработать, тестировать, расширять, и поддерживать.

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

Поддержка Prism разработки модульных приложений


Prism помогает разрабатывать модульные приложения и управлять модулями во время выполнения. Используя функциональность Prism, вы можете сэкономить время, так как вам не нужно реализовывать и тестировать свою собственную платформу для построения модульных приложений. Prism поддерживает следующие функции для разработки модульных приложений:
  • Каталог модулей для регистрации именованных модулей, или определения их расположения; можно создать каталог модулей следующими способами:
    • Определяя модули в коде, или в XAML.
    • Для WPF: обнаруживая модули в каталоге, таким образом, можно загрузить все модули, не определяя их явно в централизованном каталоге.
      Заметка.
      При использовании этого подхода, сборки с модулями могут не компилироваться автоматически, при сборке проекта, и не копироваться в выходную директорию, так как на них отсутствуют ссылки из проекта исполняемого файла.
    • Для WPF: определяя модули в конфигурационном файле.

  • Декларативно задавать атрибуты для модулей, служащие для определения режима инициализации и внедрения зависимостей.
  • Интеграция с контейнерами внедрения зависимостей, для поддержания слабой связанности между модулями.
  • Для загрузки модулей:
    • Управление зависимостями, включая дублирование и обнаружение циклов, для гарантирования того, что модули загрузятся в правильном порядке и что они загрузятся и инициализируются лишь один раз.
    • Фоновая загрузка модулей и загрузка по требованию, чтобы минимизировать время запуска приложения; оставшаяся часть модулей может быть загружена и инициализирована в фоновом режиме или по необходимости.


Базовые понятия


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

IModule: строительный блок модульных приложений


Модуль является логическим набором функциональных компонент и ресурсов, собранных в одном месте. Модули могут быть разработаны, протестированы, развернуты, и интегрированы в приложение по отдельности. Пакет может включать одну или более сборок, которые могут располагаться по отдельности, или собираться в едином XAP файле. У каждого модуля имеется центральный класс, который ответственен за инициализацию и интеграцию функциональности модуля в приложение. Этот класс реализует интерфейс IModule. Присутствия класса, реализующего интерфейс IModule, достаточно, чтобы идентифицировать пакет как модуль. У интерфейса IModule есть единственный метод Initialize, в пределах которого можно реализовать любую логику, необходимую для инициализации и интеграции функциональности модуля в приложение. В зависимости от цели модуля, он может регистрировать представления в регионах пользовательского интерфейса, делать доступными для приложения дополнительные службы, или расширять его функциональность. Следующий код показывает минимальную реализацию модуля.

public class MyModule : IModule {
    public void Initialize() {
        // Код инициализации расположен здесь.
    }
}

Заметка
Вместо использования механизма инициализации, предоставляемого интерфейсом IModule, Stock Trader RI использует декларативный, основанный на атрибутах подход для того, чтобы зарегистрировать представления, службы, и типы.

Время жизни модуля


Процесс загрузки модуля в Prism включает следующее:
  1. Регистрация/обнаружение модулей. Модули, которые будут загружены во время выполнения для определенного приложения, определяются в каталоге модулей. Каталог содержит информацию о загружаемых модулях, их расположение, и порядок, в котором они должны быть загружены.
  2. Загрузка модулей. Сборки, которые содержат модули, загружаются в память. Эта фаза может потребовать загрузки модуля из сети или из другого удаленного расположения, или локального каталога.
  3. Инициализация модулей. Создание класса модуля и вызов метода Initialize через интерфейс IModule.

Следующая иллюстрация показывает процесс загрузки модуля.

Процесс загрузки модуля

Каталог модулей


ModuleCatalog содержит информацию о модулях, которые могут использоваться приложением. Каталог является, по существу, набором классов ModuleInfo. Каждый модуль описывается классом ModuleInfo, который хранит имя, тип и расположение модуля. Есть несколько типичных подходов к заполнению ModuleCatalog экземплярами ModuleInfo:
  • Регистрация модулей в коде.
  • Регистрация модулей в XAML.
  • Регистрация модулей в конфигурационном файле (только в WPF).
  • Обнаружение модулей в локальном каталоге на диске (только в WPF).

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

Управление моментом загрузки модуля


Приложения Prism могут инициализировать модули при первой возможности, что известно как «when available», или когда приложение нуждается в них, что известно как «on-demand». Для приложений Silverlight, модули могут быть загружены вместе с приложением, или в фоновом режиме после того, как приложение запустится. Рассмотрите следующие инструкции по загрузке модулей:
  • Модули, требуемые для работы приложения, должны быть загружены вместе с приложением и инициализированы при старте.
  • Модули, содержащие функции, которые почти всегда используются при обычном использовании приложения, могут быть загружены в фоновом режиме и инициализированы, когда они становятся доступными.
  • Модули, содержащие те функции, которые редко используются (или модули поддержки, от которых другие модули могут зависеть), могут быть загружены в фоновом режиме и инициализированы по требованию.

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

Интеграция модулей с приложением


Prism предоставляет следующие классы, необходимые для загрузки вашего приложения: UnityBootstrapper и MefBootstrapper. Эти классы могут использоваться для создания и конфигурирования менеджера модулей, который необходим для обнаружения и загрузки модулей. Можно переопределить метод конфигурации каталога модулей, чтобы зарегистрировать модули, определенные в файле XAML, конфигурационном файле, или указать расположение каталога.

Используйте метод Initialize модуля, чтобы интегрировать модуль с остальной частью приложения. Способ, которым вы это сделаете, будет зависеть от структуры приложения и содержимого модуля. Ниже показаны общие шаги, которые необходимо совершить для интеграции модуля в ваше приложение:
  • Добавьте представления, содержащиеся в модуле, к структуре навигации приложения. Это общий шаг при создании UI составного приложения, используя обнаружение, или инжекцию представлений.
  • Подпишитесь на события уровня приложения или службы.
  • Зарегистрируйте совместно используемые службы в контейнере внедрения зависимостей приложения.

Поддержка связи между модулями


Даже учитывая, что модули должны быть слабо связаны, им свойственно обмениваться друг с другом данными и сообщениями. Есть несколько коммуникационных паттернов для слабо связанных систем, каждый со своими преимуществами и недостатками. Как правило, в конечном счёте, используются комбинации этих паттернов. Далее представлены некоторые из этих паттернов:
  • Слабо связанные события. Модуль может широковещательно передать, что имело место определённое событие. Другие модули могут подписаться на получение таких событий. Таким образом, они будут уведомлены, когда это событие произойдёт. Слабо связанные события являются довольно лёгким способом установления связи между модулями. В связи с этим, это легко реализуется. Однако, проект, который слишком полагается на события, может стать трудным для поддержки, особенно если событий много, или они должны быть организованы вместе для выполнения единственной задачи. В этом случае, использование общей службы может быть более рациональным.
  • Совместно используемые службы. Совместно используемая служба является классом, к которому можно получить доступ через общий интерфейс, обычно разрешаемый через контейнер. Как правило, совместно используемые службы находятся в совместно используемой сборке и предоставляют услуги в масштабе всей системы, такие как аутентификация, журналирование, или конфигурация. Такие службы часто являются синглтонами.
  • Совместно используемые ресурсы. Если вы не хотите, чтобы модули непосредственно связывались друг с другом, вы можете сделать так, чтобы они связались косвенно через совместно используемый ресурс, такой как база данных или ряд веб-сервисов.

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


Контейнеры, такие как 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, как:
  • Ассоциация типов модуля с расположением их XAP файлов.
  • Регистрация модулей через XAML и в коде для WPF и Silverlight.
  • Регистрация модулей через конфигурационные файлы и сканируемые каталоги для WPF.
  • Отслеживание состояния загрузки модулей.
  • Пользовательские декларативные метаданные для модулей.

Ключевые решения


Первое решение, которое вы должны принять, состоит в том, хотите ли вы разработать модульное приложение. Есть многочисленные преимущества создания модульных приложений, как было обсуждено в предыдущем разделе, но также существует дополнительное время и усилия, которое вы должны приложить, чтобы получить эти преимущества. Если вы всё же решите разрабатывать модульное приложение, то есть ещё несколько вещей для рассмотрения:
  • Определите платформу, которую вы будете использовать. Можно создать свою собственную модульную платформу, использовать Prism, MEF, или другую платформу.
  • Определите, как организовать ваше приложение. Приблизьтесь к модульной архитектуре, определив границы каждого модуля, включая то, какие сборки являются частью каждого модуля. Можно решить использовать модульный принцип, чтобы облегчить разработку, для управления тем, как приложение будет развёрнуто, или для поддержки сменной или расширяемой архитектуры.
  • Определите, как разделить ваши модули. Модули могут быть разделены, основываясь на требованиях, например, по функциональным областям, предоставленным модулям, группам разработчиков и требованиям развёртывания.
  • Определите базовые службы, которые приложение предоставит всем модулям. К примеру, это может быть служба сообщения об ошибках, или служба аутентификации и авторизации.
  • Если вы используете Prism, определяете, как вы будете регистрировать модули в каталоге модулей. Для WPF можно зарегистрировать модули в коде, в XAML, в конфигурационном файле, или настроить обнаружение модулей в локальном каталоге на диске. Для Silverlight можно зарегистрировать модули в коде или в XAML.
  • Определите свою стратегию передачи данных и зависимостей между модулями. Модули должны будут связываться друг с другом, и вы должны будете иметь дело с зависимостями между модулями.
  • Определите контейнер внедрения зависимостей. Как правило, модульные системы требуют внедрения зависимостей, инверсии управления, или локатора служб для обеспечения слабой связанности, динамической загрузки, и создания модулей. Prism позволяет сделать выбор между Unity, MEF, или другим контейнером и предоставляет библиотеки для приложений на основе Unity или MEF.
  • Минимизируйте время запуска приложения. Подумайте о загрузке модулей по требованию и фоновой загрузке, чтобы минимизировать время запуска приложения.
  • Определите требования развёртывания. Вы должны будете подумать о том, как вы намереваетесь развёртывать своё приложение. Это может влиять на число сборок собираемых в XAP файлы. Вы могли бы также разделить совместно использованные библиотеки, такие как Prism, чтобы использовать сборки, кэширующиеся в Silverlight.

Следующие разделы детально рассматривают эти решения.

Разделение приложения на модули


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

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

Приложение с модулями, организованными вокруг вертикального среза

Приложение с модулями, организованными вокруг горизонтальных слоёв

Большое приложение может иметь модули, организованные как по вертикальным срезам, так и по горизонтальным слоям. Например, модули могут включать следующее:
  • Модуль, который содержит определённую функцию приложения, такую как модуль новостей в Stock Trader Reference Implementation (Stock Trader RI).
  • Модуль, который содержит определённую подсистему или функциональность для ряда связанных разделов использования, таких как покупка, выставление счёта, или главная бухгалтерская книга.
  • Модуль, который содержит службы инфраструктуры, такие как журналирование, кэширование, и службы авторизации, или веб-сервисы.
  • Модуль, который содержит службы, вызывающие line-of-business (LOB) системы, такие как Siebel CRM и SAP, в дополнение к другим внутренним системам.

У модуля должен быть минимальный набор зависимостей от других модулей. Когда у модуля есть зависимость от другого модуля, он должен быть слабо связан с ним через интерфейсы, определённые в совместно используемой библиотеке, а не через конкретные типы, или, используя EventAggregator, чтобы связаться с другими модулями через события.

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

Определение соотношения проектов и модулей

Есть несколько способов создания и упаковки модули. Рекомендуемый и наиболее распространённый способ заключается в создании единственной сборки на модуль. Это помогает разделить модули логически и способствует надлежащей инкапсуляции. Это также позволяет говорить о сборке как о модуле и наоборот. Однако ничто не препятствует тому, чтобы единственная сборка содержала несколько модулей, в некоторых случаях это может быть предпочтительным для минимизации числа проектов в вашем решении. Большое приложение вполне может иметь 10-50 модулей. Перенос каждого модуля в свой собственный проект добавляет сложности к решению и может замедлить производительность Visual Studio. Иногда имеет смысл разделить модуль или набор модулей на собственные решения, если вы хотите придерживаться одного модуля на проект или сборку.

XAP и факторинг модуля

Для приложений Silverlight модули обычно упаковываются в отдельных файлах XAP, хотя в некоторых случаях, у вас может быть больше чем один модуль на XAP. Следует рассмотреть, в каком количестве файлов XAP вы нуждаетесь, чтобы минимизировать число и размер запросов загрузки, требуемых для запуска приложения и активации новой опции. Если вы хотите разделять каждый модуль на его собственный проект/сборку, вы должны решить, вставить ли каждую сборку его собственный XAP для развёртывания или включать несколько сборок в единственный XAP.

Некоторые факторы, влияющие на ваш выбор того, включать ли несколько модулей в единственный файл XAP или разделить их:
  • Размер загрузки и совместно использованные зависимости. У каждого файла XAP есть небольшое количество дополнительных издержек размера в его декларации и .zip упаковке. Кроме того, если будут общие зависимости между модулями, и они не относятся к зависимому модулю или кэшируемой библиотеке, то каждый XAP будет включать эти зависимые библиотеки, которые могут значительно увеличить размер загрузки.
  • Синхронизация того, когда несколько модулей необходимы приложению. Если загружаются и используются одновременно несколько модулей, такие как предоставление представлений при запуске приложения, упаковка их в единственном файле XAP может сделать загрузку немного быстрее и позволит гарантировать, что оба модуля станут физически доступными клиенту одновременно. Функция модульного принципа Prism гарантирует, что модули, которые указывают на зависимости, загрузятся в правильном порядке. Но есть небольшое количество издержек производительности, включенных в создание двух загрузок вместо одной, даже если суммарный размер загрузки одинаков.
  • Управление версиями модулей. Если различные модули будут разработаны на независимых временных шкалах и потенциально развернуты отдельно, вы можете захотеть поместить их в отдельные XAP, чтобы они могли быть отмечены различными версиями более чисто и обновлены независимо.

Чтобы избежать загружать одной и те же сборки несколько раз в каждом XAP, есть два подхода, которые могут использоваться:
  • Разложить совместно использованные зависимости в отдельный модуль инфраструктуры, и иметь потребляемые модули, берущие зависимости от этого совместно используемого модуля.
  • Пользуйтесь Assembly Library Caching в Silverlight, чтобы поместить совместно используемые типы в совместно используемую библиотеку, которая загружается один раз и кэшируется Silverlight, а не загрузчиком модулей Prism.

Используйте внедрение зависимостей для достижения слабой связанности


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

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

Пример внедрения зависимостей.

В этом примере сборка OrdersModule определяет класс OrdersRepository (наряду с другими представлениями и классами, которые реализуют функциональность заказа). Сборка CustomerModule определяет класс CustomersViewModel, который зависит от класса OrdersRepository, основанного на интерфейсе, предоставленном службой. Запуск приложения и процесс загрузки содержит следующие шаги:
  1. Bootstrapper запускает процесс инициализации модуля, и загрузчик модуля загружает и инициализирует OrdersModule.
  2. В этапе инициализации OrdersModule, он регистрирует OrdersRepository в контейнере.
  3. Загрузчик модуля загружает CustomersModule. Порядок загрузки модулей может быть определён на основе их метаданных.
  4. CustomersModule создаёт экземпляр CustomerViewModel, разрешая его через контейнер. CustomerViewModel имеет зависимость от OrdersRepository (основанную на его интерфейсе) и указывает на это через инжекцию свойства или конструктора. Контейнер вводит эту зависимость при создании модели представления, основанной на типе, зарегистрированном OrdersModule. Конечным результатом является интерфейсная ссылка из CustomerViewModel на OrderRepository без жёсткой связи между этими классами.

Заметка
Интерфейс, используемый, чтобы представить 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 определяет, какой нужно создать класс каталога модулей и какие модули к нему добавить. Обычно .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, чтобы добавить функциональность модуля к событиям и службам уровня приложения. Например, приложение может генерировать событие, когда оно завершает работу, и ваш модуль хочет реагировать на это событие. Также возможно, что ваш модуль должен будет предоставлять некоторые данные для службы уровня приложения. Например, если вы создали службу MenuService (ответственную за добавление и удаление пунктов меню), метод Initialize модуля – то место, где можно добавлять корректные пункты меню.
    Заметка
    Время жизни экземпляра модуля по умолчанию является недолгим. После того, как вызывают метод Initialize во время процесса загрузки, ссылка на экземпляр модуля удаляется. Если вы не создадите цепочку ссылок на экземпляр модуля, то он будет собран сборщиком мусора. Такое поведение может быть нежелательным при отладке, если вы подписываетесь на события, которые содержат слабую ссылку на ваш модуль, потому что ваш модуль «исчезает» при запуске сборщика мусора.
  • Регистрация типов в контейнере внедрения зависимостей. Если вы используете контейнер внедрения зависимостей, такой как Unity или MEF, модуль может зарегистрировать типы для использования их в приложении или в других модулях. Также можно использовать контейнер для разрешения экземпляров типов, в которых нуждается модуль.


Определение зависимостей модуля


Модули могут зависеть от других модулей. Если Module A зависит от Module B, то Module B должен быть инициализирован перед Module A. ModuleManager отслеживает эти зависимости и инициализирует модули в правильном порядке. В зависимости от того, как вы определили свой каталог модулей, можно определить свои зависимости модуля в коде, конфигурации, или XAML.

Определение зависимостей в коде

Для приложений WPF, которые регистрируют модули в коде или обнаруживают их в папке, Prism предоставляет декларативные атрибуты для класса модуля, как показано в следующем примере.

[Module(ModuleName = "ModuleA")]
[ModuleDependency("ModuleD")]
public class ModuleA: IModule {
    ...
}

Определение зависимостей в XAML

Следующий 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
  });
}

Задание загрузки по требованию в XAML

Можно определить 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 в качестве контейнера внедрения зависимостей.
Заметка
При использовании MEF, MefBootstrapper использует MefModuleManager. Он расширяет ModuleManager и реализует интерфейс IPartImportsSatisfiedNotification, чтобы гарантировать, что ModuleCatalog обновляется, когда новые типы импортируются MEF.

Регистрация модулей в коде, используя 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, чтобы предотвратить инстанцирование экспортируемых и импортированных типов до использования свойства Value.
Обнаружение модулей в локальном каталоге, используя MEF

MEF предоставляет класс DirectoryCatalog, который может использоваться, чтобы просмотреть папку для сборок, содержащих модули (и другие экспортируемые MEF типы). В этом случае, вы переопределяете метод ConfigureAggregateCatalog, чтобы зарегистрировать каталог. Этот подход доступен только в WPF.

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

protected override void ConfigureAggregateCatalog() {
    base.ConfigureAggregateCatalog();

    DirectoryCatalog catalog = new DirectoryCatalog("DirectoryModules");
    this.AggregateCatalog.Catalogs.Add(catalog);
}

Задание зависимостей в коде, используя MEF

Для приложений 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

Если вы используете MEF и атрибут ModuleExport для того, чтобы определить модули и зависимости от модуля, можно использовать свойство InitializationMode, чтобы определить, что модуль должен быть загружен по требованию, как показано ниже.

[ModuleExport(typeof(ModuleC), InitializationMode = InitializationMode.OnDemand)]
public class ModuleC : IModule { ... }

Подготовка модуля к удалённой загрузке в MEF

Под капотом, приложение 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

Чтобы узнать больше о модульном принципе в Prism, смотрите Modularity with MEF for WPF QuickStart или Modularity with Unity for WPF QuickStart. Чтобы узнать больше о QuickStarts, смотрите Modularity QuickStarts for WPF.

Для получения информации о функциях библиотеки Prism, используемых при построении модульных приложений, смотрите "Modules" в "Extending Prism."
Tags:
Hubs:
+8
Comments 0
Comments Leave a comment

Articles