logo

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

}