[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
*/
}
}