[ELMA3] Архитектура ядра системы
Ядро системы построено на основе IoC (обращение контроля) на основе контейнера Autofac. В нем регистрируются все основные компоненты системы (различные менеджеры и службы, компоненты расширения, веб-контроллеры и т.д.).
Типы жизненного цикла в контейнере
Каждый объект, зарегистрированный в контейнере Autofac имеет определенный жизненный цикл.
namespace EleWise.ELMA.ComponentModel { /// <summary> /// Тип жизненного цикла в контейнере /// </summary> public enum ServiceScope { /// <summary> /// Один объект на приложение /// </summary> Application = 0x1, /// <summary> /// Один объект на контейнер (контейнер пересоздается при включении-отключении расширений) /// </summary> Shell = 0x0, /// <summary> /// Один экземпляр на каждое использование /// </summary> Transient = 0x2, /// <summary> /// Один экземпляр на единицу работы (в Web будет один экземпляр на HTTP запрос) /// </summary> UnitOfWork = 0x3 } }
Автоматически регистрируемые службы
Классы, помеченные атрибутом EleWise.ELMA.Attributes.ServiceAttribute автоматически регистрируются в контейнере Autofac.
namespace EleWise.ELMA.ComponentModel { /// <summary> /// Атрибут службы, автоматически регистрируемой в контейнере Autofac /// </summary> [AttributeUsage(AttributeTargets.Class)] public class ServiceAttribute : Attribute { /// <summary> /// Автоматически присвоить значения свойств /// </summary> [DefaultValue(true)] public bool InjectProperties { get; set; } /// <summary> /// Тип жизненного цикла /// </summary> [DefaultValue(ServiceScope.Applcation)] public ServiceScope Scope { get; set; } /// <summary> /// Применять перехватчики методов к классу /// </summary> [DefaultValue(true)] public bool EnableInterceptors { get; set; } } }
Пример автоматически регистрируемой службы:
/// <summary> /// Регистрируется с типами SomeService и ISomeService /// </summary> [Service] public class SomeService : ISomeService { ... }
Автоматически регистрируемые объекты
В контейнере Autofac автоматически регистрируются следующие объекты
Компоненты (см. статью Компонентная модель).
Регистрируются с типом класса компонента и типами всех реализуемых точек расширения.
/// <summary> /// Компонент регистрируется с типом CustomMenuExtension, а также IEnumerable<IMenuExtension>. /// </summary> [Component] public class CustomMenuExtension : IInitHandler, IMenuExtension { ... }
Автоматически регистрируемые службы.
См. пункт выше Автоматически регистрируемые службы.
Менеджеры сущностей.
Классы, реализующие интерфейс IEntityManager.
/// <summary> /// Контроллер веб-приложения - регистрируется с типом MenuController /// </summary> public class MenuController : BaseController<Menu, long> { ... }
Ручная регистрация в контейнере
Ручную регистрацию следует применять только в том случае, когда способы автоматической регистрации не устраивают по какой-либо причине.
Существует 2 способа для ручной регистрации
Способ 1. При инициализации системы
Для этого необходимо создать компонент с реализацией точки расширения EleWise.ELMA.ComponentModel.IInitHandler и в методе Init() производить регистрацию, используя статическое свойство ComponentManager.Builder.
Для доступа к методам-расширениям, применяемым для регистрации в контейнере необходимо добавить ссылку на сборку Autofac и прописать использование пространства имен using Autofac.
Возможности при регистрации:
- Установка регистрируемого типа – метод RegisterType() или RegisterType(typeof(TService));
- Установка типов, под которыми будет доступен данный объект – метод As() или As(typeof(TRegisrationType));
- Установка типа жизненного цикла – метод-расширение SetScope(...) из пространства имен Autofac. По умолчанию (без вызова метода SetScope) используется жизненный цикл Transient - один экземпляр на каждое использование;
- Автоматическая инициализация свойств – метод PropertiesAutowired(...).
Пример:
using Autofac; using EleWise.ELMA.ComponentModel; /// <summary> /// Некий менеджер, который нужно зарегистрировать в контейнере Autofac. /// </summary> public class SomeManager { ... } /// <summary> /// Класс, регистрирующий SomeManager /// </summary> [Component] public class SomeManagerRegistrar : IInitHandler { /// <summary> /// Начало инициализации - регистрируем SomeManager с типом жизненного цикла Application и автоматической установкой свойств. /// </summary> public void Init() { ComponentManager.Builder .RegisterType<SomeManager>() .SetScope(ServiceScope.Applcation) .PropertiesAutowired(false); } /// <summary> /// Завершение инициализации /// </summary> public void InitComplete() { } }
Способ 2. В любой момент времени после инициализации системы
Применять данный способ необходимо только в том случае, если не устраивает автоматическая активация и способ 1. Используется статический класс EleWise.ELMA.Services.Locator и один из методов AddService.
namespace EleWise.ELMA.Services { /// <summary> /// Менеджер служб /// </summary> public static class Locator { /// <summary> /// Признак, что менеджер инициализирован /// </summary> public static bool Initialized { get; } /// <summary> /// Зарегистрировать службу /// </summary> /// <param name="type">Тип службы</param> /// <param name="obj">Служба</param> public static void AddService(Type type, object obj); /// <summary> /// Зарегистрировать службу /// </summary> /// <param name="type">Тип службы</param> /// <param name="obj">Служба</param> /// <param name="resolveProperties">Обрабатывать публичные свойства при регистрации</param> public static void AddService(Type type, object obj, bool resolveProperties); /// <summary> /// Зарегистрировать существующую службу /// </summary> /// <typeparam name="T">Тип службы</typeparam> /// <param name="obj">Служба</param> public static void AddService<T>(T obj); /// <summary> /// Зарегистрировать существующую службу /// </summary> /// <typeparam name="T">Тип службы</typeparam> /// <param name="obj">Служба</param> /// <param name="resolveProperties">Обрабатывать публичные свойства при регистрации</param> public static void AddService<T>(T obj, bool resolveProperties); /// <summary> /// Разрегистрировать службу /// </summary> /// <param name="type">Тип службы</param> public static void RemoveService(Type type); } }
Пример:
public class SomeClass { public void SomeMethod() { ... if (!Locator.Initialized) { throw new InvalidOperationException("Локатор не инициализирован"); } Locator.AddService<SomeService>(new SomeService()); } }
Получение объектов из контейнера
Для получения объектов из контейнера Autofac существует 2 способа. Предпочтительным является 1-й (если он применим в конкретном случае).
Способ 1. Автоматически инициализируемые свойства (Auto-injected)
Применяется в том случае, если объект, в котором требуется получить компоненты (допустим, MenuController), помещен в контейнер Autofac, и для него активирована автоматическая инициализация свойств (PropertiesAutowired).
Для получения объекта определенного типа из контейнера необходимо просто объявить свойство.
public class MenuController : Controller { ... public ISomeService SomeService{ get; set; } ... }
При создании объекта MenuController свойство SomeService будет заполнено автоматически, если сервис с типом ISomeService зарегистрирован. Если не зарегистрирован, то в свойстве будет Null.
Способ 2. Использование локатора служб
Используется статический класс EleWise.ELMA.Services.Locator.
namespace EleWise.ELMA.Services { /// <summary> /// Менеджер служб /// </summary> public static class Locator { /// <summary> /// Признак, что менеджер инициализирован /// </summary> public static bool Initialized { get; } /// <summary> /// Получить службу с указанным типом и именем, с проверкой существования службы или без нее /// </summary> /// <param name="type">Тип службы</param> /// <param name="name">Имя службы</param> /// <param name="checkNotNull">Нужно ли проверить, чтобы служба сущствовала</param> /// <returns>Запрашиваемая служба</returns> /// <exception cref="EleWise.ELMA.Exceptions.NotInitializedException">Если менеджер служб не инициализирован</exception> /// <exception cref="EleWise.ELMA.Exceptions.ServiceNotFoundException">Если запрашиваемая служба не найдена</exception> [CanBeNull] public static object GetService(Type type, string name, bool checkNotNull); /// <summary> /// Получить службу с указанным типом и именем (без проверки существования) /// </summary> /// <param name="type">Тип службы</param> /// <param name="name">Имя службы</param> /// <returns>Запрашиваемая служба</returns> /// <exception cref="EleWise.ELMA.Exceptions.NotInitializedException">Если менеджер служб не инициализирован</exception> [CanBeNull] public static object GetService(Type type, string name); /// <summary> /// Получить службу с указанным типом и именем (с проверкой существования) /// </summary> /// <param name="type">Тип службы</param> /// <param name="name">Имя службы</param> /// <returns>Запрашиваемая служба</returns> /// <exception cref="EleWise.ELMA.Exceptions.NotInitializedException">Если менеджер служб не инициализирован</exception> /// <exception cref="EleWise.ELMA.Exceptions.ServiceNotFoundException">Если запрашиваемая служба не найдена</exception> [NotNull] public static object GetServiceNotNull(Type type, string name); /// <summary> /// Получить службу с указанным типом (без проверки существования) /// </summary> /// <param name="type">Тип службы</param> /// <returns>Запрашиваемая служба</returns> /// <exception cref="EleWise.ELMA.Exceptions.NotInitializedException">Если менеджер служб не инициализирован</exception> [CanBeNull] public static object GetService(Type type); /// <summary> /// Получить службу с указанным типом (с проверкой существования) /// </summary> /// <param name="type">Тип службы</param> /// <returns>Запрашиваемая служба</returns> /// <exception cref="EleWise.ELMA.Exceptions.NotInitializedException">Если менеджер служб не инициализирован</exception> /// <exception cref="EleWise.ELMA.Exceptions.ServiceNotFoundException">Если запрашиваемая служба не найдена</exception> [NotNull] public static object GetServiceNotNull(Type type); /// <summary> /// Получить службу с указанным типом (без проверки существования) /// </summary> /// <typeparam name="T">Тип службы</typeparam> /// <returns>Запрашиваемая служба</returns> /// <exception cref="EleWise.ELMA.Exceptions.NotInitializedException">Если менеджер служб не инициализирован</exception> [CanBeNull] public static T GetService<T>(); /// <summary> /// Получить службу с указанным типом (с проверкой существования) /// </summary> /// <typeparam name="T">Тип службы</typeparam> /// <returns>Запрашиваемая служба</returns> /// <exception cref="EleWise.ELMA.Exceptions.NotInitializedException">Если менеджер служб не инициализирован</exception> /// <exception cref="EleWise.ELMA.Exceptions.ServiceNotFoundException">Если запрашиваемая служба не найдена</exception> [NotNull] public static T GetServiceNotNull<T>(); /// <summary> /// Получить службу с указанным типом и именем (без проверки существования) /// </summary> /// <typeparam name="T">Тип службы</typeparam> /// <param name="name">Имя службы</param> /// <returns>Запрашиваемая служба</returns> /// <exception cref="EleWise.ELMA.Exceptions.NotInitializedException">Если менеджер служб не инициализирован</exception> [CanBeNull] public static T GetService<T>(string name); /// <summary> /// Получить службу с указанным типом и именем (с проверкой существования) /// </summary> /// <typeparam name="T">Тип службы</typeparam> /// <param name="name">Имя службы</param> /// <returns>Запрашиваемая служба</returns> /// <exception cref="EleWise.ELMA.Exceptions.NotInitializedException">Если менеджер служб не инициализирован</exception> /// <exception cref="EleWise.ELMA.Exceptions.ServiceNotFoundException">Если запрашиваемая служба не найдена</exception> [NotNull] public static T GetServiceNotNull<T>(string name); } }
Пример:
using EleWise.ELMA.Services; public class SomeClass { public void SomeMethod() { var someService = Locator.GetService<ISomeService>(); if (someService != null) { ... } /*
Или так:
var someService = Locator.GetServiceNotNull<ISomeService>(); Если сервис не найден - будет выдана ошибка EleWise.ELMA.Exceptions.ServiceNotFoundException */ } }