[ELMA3] Компонентная модель
Общая информация
Компонентная модель позволяет расширять произвольные части системы. Например: добавлять пункты меню, кнопки, производить какую-либо обработку данных и т.д.
В рамках компонентной модели выделим несколько понятий:
- Расширяемый модуль – модуль системы, который использует определенный набор точек расширения;
- Точка расширения – произвольный интерфейс с набором свойств/методов, который помечен атрибутом EleWise.ELMA.ComponentModel.ExtensionPointAttribute;
- Компонент – экземпляр класса, реализующего точку расширения (интерфейс), и помеченный атрибутом ComponentAttribute. Для одной точки расширения может быть несколько реализаций (компонентов).
Для инициализации и работы с ядром системы существует менеджер компонентов – класс EleWise.ELMA.ComponentModel.ComponentManager. Он позволяет инициализировать компонентную модель, регистрировать и получать компоненты. При запуске системы менеджер компонентов загружает сборки, помеченные атрибутом EleWise.ELMA.ComponentModel.ComponentAssemblyAttribute и обрабатывает их на наличие компонентов.
Основные классы для работы с компонентной моделью расположены в сборке EleWise.ELMA.SDK в пространстве имен EleWise.ELMA.ComponentModel.
Типы, используемые в компонентной модели
- • ExtensionPointAttribute – атрибут для объявления интерфейса-расширения;
• ComponentManager – менеджер доступа к загруженным компонентам;
• ComponentAssemblyAttribute – атрибут для объявления сборки, содержащей компоненты;
• ComponentAttribute – атрибут для объявления компонента;
• IInitHandler – точка расширения для обработки загрузки компонентов.
Создание точки расширения
Для создания точки расширения необходимо создать интерфейс и пометить его атрибутом EleWise.ELMA.ComponentModel.ExtensionPointAttribute.
namespace EleWise.ELMA.ComponentModel { /// <summary> /// Атрибут интерфейса точки расширения /// </summary> [AttributeUsage(AttributeTargets.Interface)] public class ExtensionPointAttribute : Attribute { /// <summary> /// Точка расширения с типом жизненного цикла Application и регистрацией экземпляров компонентов /// </summary> public ExtensionPointAttribute(); /// <summary> /// Точка расширения с указанным типом регистрации компонентов (регистрация типов компонентов, либо экземпляров компонента) /// </summary> /// <param name="createInstance">Если false, то регистрируются только типы компонентов, реализующих данную точку расширения</param> public ExtensionPointAttribute(bool createInstance); /// <summary> /// Точка расширения с указанным типом жизненного цикла /// </summary> /// <param name="serviceScope">Тип жизненного цикла компонентов, реализующих данную точку расширения</param> public ExtensionPointAttribute(ServiceScope serviceScope); /// <summary> /// Создавать ли экземпляры компонентов /// </summary> /// <remarks> /// True, если нужно создать экземпляры (после загрузки они доступны через метод IComponentManager.GetExtensionPoints). /// False, если нужно загружать только их типы (после загрузки они доступны через метод IComponentManager.GetExtensionPointTypes). /// </remarks> public bool CreateInstance { get; } /// <summary> /// Контекст, в котором будут зарегистрированы и созданы компоненты, реализующие точку расширения /// Applcation - регистрация на уровне приложения (до инициализации IInitHandler.Init), один экземпляр на приложение /// Shell - регистрация уровне контейнера (контейнер пересоздается после включения/отключения расширений), один экземпляр на контейнер /// Transient - регистрация уровне контейнера, экземпляр создается на пождому запросу из контейнера /// UnitOfWork - регистрация уровне контейнера, экземпляр создается на каждый UnitOfWork (в Web сонтексте на каждый HTTP запрос) /// </summary> public ServiceScope ServiceScope { get; } } }
Пример:
/// <summary> /// Точка расширения для меню /// </summary> [ExtensionPoint] public interface IMenuExtension { /// <summary> /// Получить список пунктов меню. /// </summary> MenuItem[] GetMenuItems(); }
Создание компонента
Для создания компонента нужно проверить наличие у сборки, в которой он создается, атрибута EleWise.ELMA.ComponentModel.ComponentAssemblyAttribute. Если его нет, нужно прописать.
[assembly: EleWise.ELMA.ComponentModel.ComponentAssembly]
Затем создать класс, реализовать интерфейс точки расширения и пометить атрибутом EleWise.ELMA.ComponentModel.ComponentAttribute.
namespace EleWise.ELMA.ComponentModel { /// <summary> /// Атрибут компонента, реализующего точки расширений /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = false)] public class ComponentAttribute : Attribute { /// <summary> /// Порядок, в котором выстраивается список компонентов, реализующих определенную точку расширения /// </summary> [DefaultValue(0)] public int Order { get; set; } /// <summary> /// Автоматически инициализировать значения свойств (через контейнер Autofac) /// </summary> [DefaultValue(true)] public bool InjectProerties { get; set; } /// <summary> /// Применять перехватчики методов к классу /// </summary> [DefaultValue(false)] public bool EnableInterceptiors { get; set; } } }
Пример:
/// <summary> /// Компонент, реализующий точку расширения IMenuExtension /// </summary> [Component] public class CustomMenuExtension : IMenuExtension { public MenuItem[] GetMenuItems() { ... } }
Менеджер компонентов
Менеджер компонентов создается при запуске системы и может быть получен после его создания через статическое свойство ComponentManager.Current.
namespace EleWise.ELMA.ComponentModel
{ /// <summary> /// Менеджер компонентов /// </summary> public class ComponentManager : IComponentManager, IDisposable { /// <summary> /// Этап жизненного цикла /// </summary> public enum LifetimeStage { /// <summary> /// До начала инициализации /// </summary> BeforeInit, /// <summary> /// В момент вызова IInitHandler.Init /// </summary> Initializing, /// <summary> /// В момент вызова IInitHandler.InitComplete /// </summary> InitCompleting, /// <summary> /// После инициализации /// </summary> Initialized, /// <summary> /// Уничтожен /// </summary> Disposed } /// <summary> /// Получить текущий менеджер /// </summary> public static ComponentManager Current { get; } /// <summary> /// Инициализирован или нет /// </summary> public static bool Initialized { get; } /// <summary> /// Текущий контейнер IoC (доступен только в процессе начала инициализации - в методе IInitHandler.Init) /// </summary> public static ContainerBuilder Builder { get; } /// <summary> /// Этап жизненного цикла /// </summary> public LifetimeStage Stage { get; } /// <summary> /// Зарегистрировать существующий компонент. Метод доступен на этапах BeforeInit и Initializing. /// </summary> /// <param name="component">Компонент</param> public ComponentManager RegisterComponent(object component); /// <summary> /// Зарегистрировать сборку, в которой будет искаться компоненты. Метод доступен на этапах BeforeInit и Initializing. /// </summary> /// <param name="assembly">Сборка</param> public ComponentManager RegisterAssembly(Assembly assembly); /// <summary> /// Возвращает компонент определенного типа. Метод доступен на этапах Initializing, InitCompleting и Initialized. /// </summary> /// <typeparam name="T">Тип расширения</typeparam> /// <returns>Экземпляр расширения</returns> public T GetExtensionPointByType<T>(); /// <summary> /// Возвращает компонент, определенного типа. Метод доступен на этапах Initializing, InitCompleting и Initialized. /// </summary> /// <param name="type">Тип расширения</param> /// <returns>Экземпляр расширения</returns> public object GetExtensionPointByType(Type type); /// <summary> /// Возвращает компоненты, реализующие интерфейс-расширение. Метод доступен на этапах Initializing, InitCompleting и Initialized. /// </summary> /// <typeparam name="T">Тип интерфейса расширения</typeparam> /// <returns></returns> public IEnumerable<T> GetExtensionPoints<T>(); /// <summary> /// Возвращает компоненты, реализующие интерфейс-расширение. Метод доступен на этапах Initializing, InitCompleting и Initialized. /// </summary> /// <param name="type">Тип интерфейса расширения</param> /// <returns></returns> public IEnumerable<object> GetExtensionPoints(Type type); /// <summary> /// Возвращает типы компонентов, реализующих интерфейс-расширение. /// </summary> /// <param name="type">Тип интерфейса расширения</param> /// <returns>Список типов компонентов. Если не найдены - возвращается пустой список.</returns> public IEnumerable<Type> GetExtensionPointTypes(Type type); /// <summary> /// Возвращает типы компонентов, реализующих интерфейс-расширение. /// </summary> /// <typeparam name="T">Тип интерфейса расширения</typeparam> /// <returns>Список компонентов</returns> public IEnumerable<Type> GetExtensionPointTypes<T>(); /// <summary> /// Возвращает типы компонентов, реализующих интерфейс-расширение. /// </summary> /// <param name="type">Тип интерфейса расширения</param> /// <returns>Список типов компонентов. Если не найдены - возвращается пустой список.</returns> public Type[] GetExtensionPointTypesArray(Type type); /// <summary> /// Получить типы, реализующие интерфейс IXsiType. /// </summary> /// <returns></returns> public Type[] GetXsiTypes(); /// <summary> /// Получить массив всех компонентов, зарегистрированных в менеджере. Метод доступен на этапах Initializing, InitCompleting и Initialized. /// </summary> /// <returns>Массив компонентов.</returns> public object[] GetComponents(); } }
Процесс инициализации менеджера компонентов выглядит следующим образом:
- Загружаются все сборки (*.dll и *.exe), находящиеся в папке запускаемого приложения (веб-приложение или дизайнер ELMA).
- Выбираются сборки, имеющие атрибут ComponentAssemblyAttribute, а также добавленные через метод ComponentManager.RegisterAssembly.
- Из данных сборок выбираются классы, помеченные атрибутом ComponentAttributeилиServiceAttribute. Также выбираются объекты, добавленные через метод ComponentManager.RegisterComponent.
- В контейнере Autofac регистрируются классы выбранные на этапе 3:
- классы с атрибутом ComponentAttribute регистрируются с типом класса, а также типами реализуемых точек расширения;
- классы с атрибутом ServiceAttribute регистрируются с типом класс, а также всеми реализуемыми интерфейсами.
- Выбираются компоненты, реализующие точку расширения IInitHandler. Для каждого из них вызывается метод Init().
- Обновляется контейнер Autofac компонентами, зарегистрированными на шаге 5.
- Выбираются компоненты, реализующие точку расширения IInitHandler. Для каждого из них вызывается метод InitComplete().
- Инициализация завершена.
Точка расширения IInitHandler
Системная точка расширения EleWise.ELMA.ComponentModel.IInitHandler позволяет подписываться на события начала (Init) и окончания (InitComplete) инициализации менеджера компонентов.
namespace EleWise.ELMA.ComponentModel { /// <summary> /// Интерфейс компонента, поддерживающего методы инициализации /// </summary> [ExtensionPoint] public interface IInitHandler { /// <summary> /// Начало инициализации (могут использоваться свойства ComponentManager.Current и ComponentManager.Builder) /// </summary> void Init(); /// <summary> /// Завершение инициализации (доступен Locator) /// </summary> void InitComplete(); } }
В методе Init можно работать с текущим менеджером компонентов - статическим свойством ComponentManager.Current, а также построителем контейнера Autofac – статическим свойством ComponentManager.Builder (см. статью Архитектура ядра системы).
В методе InitComplete можно работать с текущим менеджером компонентов, а также локатором служб Locator (см. статью Архитектура ядра системы).
Получение компонентов
Для получения компонентов, реализующих определенную точку расширения (допустим, IMenuExtension), существуют 3 способа.
Способ 1. Автоматически инициализируемые свойства (Auto-injected)
Если объект, в котором требуется получить компоненты (допустим, MenuController), помещен в контейнер Autofac (см. статью Архитектура ядра системы), то необходимо объявить свойство.
public class MenuController : Controller { ... public IEnumerable<IMenuExtension> MenuExtensions { get; set; } ... }
При создании объекта MenuController свойство MenuExtensions будет заполнено автоматически, если зарегистрирован хотя бы один компонент с реализацией точки расширения IMenuExtension. Если не зарегистрирован ни один, то в свойстве будет Null.
Способ 2. Использование менеджера компонентов (ComponentManager)
using EleWise.ELMA.Services; public class SomeClass { public void SomeMethod() { var menuExtensions = ComponentManager.Current.GetExtensionPoints<IMenuExtension>(); if (menuExtensions != null) { ... } } }
Способ 3. Использование локатора служб (Locator)
Описание класса Locator см. в статье Архитектура ядра системы
using EleWise.ELMA.Services; public class SomeClass { public void SomeMethod() { var menuExtensions = Locator.GetService<IEnumerable<IMenuExtension>>(); if (menuExtensions != null) { ... } } }
Смотри также
- Список доступных точек расширения в системе: версии 3.13, 3.15, 4.0;
- Архитектура ядра системы.