[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;
- Архитектура ядра системы.