Создание собственного обработчика свойства

В данной статье будет приведен пример работы обработчика свойства типа Дата/время. Данный обработчик можно применять для любой переменной типа Дата/время, его работа заключается в том, что при создании объекта в данное поле будет подставляться дата создания автоматически. Такое поведение будет реализовано при помощи реализации трех точек расширения:

  1. IPropertyHandler – обработчик свойства. Задает имя обработчика, его уникальный идентификатор и определяет при каких условиях данный обработчик будет отображаться (для каких типов свойств).
  2. IEntityActivationHandler – точка расширения для перехвата активации при создании сущностей через InterfaceActivator.Create, InstanceOf или EntityManager.Create. Задает поведение сущности при активации, например, задается уникальный идентификатор и дата создания сущности.
  3. EntityEventsListener – базовый класс точки расширения для прослушивания событий NHibernate для объектов. Задает поведение при различных событиях NHibernate, например, перед вставкой объекта в базу задать дату создания. Реализация данной точки расширения необходима, так как в сценариях (в Дизайнере ELMA) пользователи часто создают сущности через new Entity(), при этом активации сущности уже не произойдет, но после вызова метода Save(), код в EntityEventsListener выполнится. Подробнее про обработчиков событий можно узнать в статье "Обработчики событией NHibernate".

Пример отображения

Рис. 1. Указание обработчика для свойства типа дата/время

Обработчик для свойства Дата создания является наследником базового класса TypedPropertyHandler, который в свою очередь является реализацией точки расширения IPropertyHandler.

Сигнатура точки расширения IPropertyHandler.

/// <summary>
/// Уникальный идентификатор обработчика
/// </summary>
Guid Uid { get; }

/// <summary>
/// Имя обработчика (для текущей культуры)
/// </summary>
string Name { get; }

/// <summary>
/// Доступен ли обработчик (можно ли добавить)
/// </summary>
/// <param name="classMetadata">Метаданные класса</param>
/// <param name="propertyMetadata">Метаданные свойства, для которого нужно проверить доступность обработчика</param>
/// <param name="typeUid">Uid типа данных</param>
/// <param name="subTypeUid">Uid подтипа данных</param>
/// <param name="currentHandlers">Список текущих обработчиков</param>
/// <returns>True, если доступен</returns>
bool IsAvailableFor(
    ClassMetadata classMetadata, PropertyMetadata propertyMetadata, Guid typeUid, Guid subTypeUid,
    IEnumerable<PropertyHandlerInfo> currentHandlers);

Пример базового класса TypedPropertyHandler:

/// <summary>
/// Базовый класс обработчика свойства
/// </summary>
[Component]
public abstract class TypedPropertyHandler : IPropertyHandler
{

    /// <summary>
    /// Уникальный идентификатор обработчика
    /// </summary>
    public abstract Guid Uid
    {
        get;
    }

    /// <summary>
    /// Имя обработчика (для текущей культуры)
    /// </summary>
    public abstract string Name
    {
        get;
    }

    /// <summary>
    /// Uid типа, для которого предназначен обработчик
    /// </summary>
    protected abstract Guid? TypeUid
    {
        get;
    }

    /// <summary>
    /// Uid подтипа, для которого предназначен обработчик
    /// </summary>
    protected virtual Guid? SubTypeUid
    {
        get { return Guid.Empty; }
    }

    /// <summary>
    /// Можно ли навешивать несколько обработчиков данного типа (по умолчанию False)
    /// </summary>
    protected virtual bool Multiple
    {
        get { return false; }
    }

    /// <summary>
    /// Могут ли быть навешены другие обработчики (по умолчанию False)
    /// </summary>
    protected virtual bool AllowOtherHandlers
    {
        get { return false; }
    }

    /// <summary>
    /// Доступен ли обработчик (можно ли добавить)
    /// </summary>
    /// <param name="classMetadata">Метаданные класса</param>
    /// <param name="propertyMetadata">Метаданные свойства, для которого нужно проверить доступность обработчика</param>
    /// <param name="typeUid">Uid типа данных</param>
    /// <param name="subTypeUid">Uid подтипа данных</param>
    /// <param name="currentHandlers">Список текущих обработчиков</param>
    /// <returns>True, если доступен</returns>
    public virtual bool IsAvailableFor(
        ClassMetadata classMetadata, PropertyMetadata propertyMetadata, Guid typeUid, Guid subTypeUid,
        IEnumerable<PropertyHandlerInfo> currentHandlers)
    {
        Contract.ArgumentNotNull(classMetadata, "classMetadata");
        Contract.ArgumentNotNull(propertyMetadata, "propertyMetadata");
        var handlers = currentHandlers != null ? currentHandlers.ToList() : new List<PropertyHandlerInfo>();
        if (!Multiple)
        {
            if (handlers.FirstOrDefault(h => h.HandlerUid == Uid) != null)
            {
                return false;
            }
        }
        if (!AllowOtherHandlers)
        {
            if (handlers.Count > 0)
            {
                return false;
            }
        }
        if (TypeUid == null)
        {
            return true;
        }
        if (TypeUid.Value != typeUid)
        {
            return false;
        }
        if (SubTypeUid == null)
        {
            return true;
        }
        if (SubTypeUid.Value != subTypeUid)
        {
            return false;
        }
        return true;
    }
}

Обработчик свойства Дата создания

/// <summary>
/// Обработчик свойства "Дата создания"
/// </summary>
[Component(Order = 100)]
public class CreationDatePropertyHandler : TypedPropertyHandler
{
    /// <summary>
    /// UID данного обработчика
    /// </summary>
    public const string UID_S = "{D0C00D8A-E003-427D-9942-F52CFB77B0F0}";

    /// <summary>
    /// UID данного обработчика
    /// </summary>
    public static readonly Guid UID = new Guid(UID_S);

    /// <summary>
    /// Уникальный идентификатор обработчика
    /// </summary>
    public override Guid Uid
    {
        get { return UID; }
    }

    /// <summary>
    /// Имя обработчика (для текущей культуры)
    /// </summary>
    public override string Name
    {
        get { return SR.T("Дата создания"); }
    }

    /// <summary>
    /// Uid типа, для которого предназначен обработчик
    /// </summary>
    protected override Guid? TypeUid
    {
        get { return DateTimeDescriptor.UID; }
    }

}

Все системные обработчики являются наследниками базового класса TypedPropertyHandler, отличия лишь в названии, Uid и TypeUid

Чтобы заполнить свойство нужным значением, необходимо реализовать перехватчик активации сущности IEntityActivationHandler:

[Component]
internal class PropertyHandlersActivation : IEntityActivationHandler
{

    /// <summary>
    /// Заполнить свойства сущности
    /// </summary>
    /// <param name="entity">Сущность</param>
    public static void ActivateOnCreate(IEntity entity)
    {
        var metadata = MetadataLoader.LoadMetadata(entity.GetType(), true) as EntityMetadata;

        if (metadata == null)
        {
            return;
        }

        // Дата создания
        var creationDateProperty = metadata.Properties.FirstOrDefault(p => p.Handlers.FirstOrDefault(h => h.HandlerUid == CreationDatePropertyHandler.UID) != null);
        if (creationDateProperty != null
            && (entity.GetPropertyValue(creationDateProperty.Uid) == null
            || (DateTime)entity.GetPropertyValue(creationDateProperty.Uid) == DateTime.MinValue))
        {
            entity.SetPropertyValue(creationDateProperty.Uid, DateTime.Now);
        }
    }

    public void OnActivating(Model.Entities.IEntity entity)
    {
        ActivateOnCreate(entity);
    }

    public void OnActivated(Model.Entities.IEntity entity)
    {
            
    }

}

Метод ActivateOnCreate будет вызван в листенере EntityEventsListener в методе OnPreInsert для всех сущностей IEntity:

public override bool OnPreInsert(PreInsertEvent @event)
{
    var entity = @event.Entity as IEntity;

    if (entity == null)
    {
        return false;
    }
    // Заполнить свойства сущности.
    PropertyHandlersActivation.ActivateOnCreate(entity);

    return false;
}

Таким образом, благодаря реализации трех точек расширения Вы можете добавить свой обработчик свойств объектов.