[ELMA3] Архитектура ядра системы
Ядро системы построено на основе IoC (обращение контроля) на основе контейнера Autofac. В нем регистрируются все основные компоненты системы (различные менеджеры и службы, компоненты расширения, веб-контроллеры и т.д.).
Типы жизненного цикла в контейнере
Каждый объект, зарегистрированный в контейнере Autofac имеет определенный жизненный цикл.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | 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 ; } } } |
Пример автоматически регистрируемой службы:
1 2 3 4 5 6 7 8 | /// <summary> /// Регистрируется с типами SomeService и ISomeService /// </summary> [Service] public class SomeService : ISomeService { ... } |
Автоматически регистрируемые объекты
В контейнере Autofac автоматически регистрируются следующие объекты
Компоненты (см. статью Компонентная модель).
Регистрируются с типом класса компонента и типами всех реализуемых точек расширения.
1 2 3 4 5 6 7 8 | /// <summary> /// Компонент регистрируется с типом CustomMenuExtension, а также IEnumerable<IMenuExtension>. /// </summary> [Component] public class CustomMenuExtension : IInitHandler, IMenuExtension { ... } |
Автоматически регистрируемые службы.
См. пункт выше Автоматически регистрируемые службы.
Менеджеры сущностей.
Классы, реализующие интерфейс IEntityManager.
1 2 3 4 5 6 7 | /// <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(...).
Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | 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); } } |
Пример:
1 2 3 4 5 6 7 8 9 10 11 12 | 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).
Для получения объекта определенного типа из контейнера необходимо просто объявить свойство.
1 2 3 4 5 6 7 8 | public class MenuController : Controller { ... public ISomeService SomeService{ get ; set ; } ... } |
При создании объекта MenuController свойство SomeService будет заполнено автоматически, если сервис с типом ISomeService зарегистрирован. Если не зарегистрирован, то в свойстве будет Null.
Способ 2. Использование локатора служб
Используется статический класс EleWise.ELMA.Services.Locator.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | 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); } } |
Пример:
1 2 3 4 5 6 7 8 9 10 11 12 13 | using EleWise.ELMA.Services; public class SomeClass { public void SomeMethod() { var someService = Locator.GetService<ISomeService>(); if (someService != null ) { ... } /* |
Или так:
1 2 3 4 5 6 | var someService = Locator.GetServiceNotNull<ISomeService>(); Если сервис не найден - будет выдана ошибка EleWise.ELMA.Exceptions.ServiceNotFoundException */ } } |