В предыдущем посте я описал способ, который позволяет проводить аудит изменений данных в Entity Framework, не затрагивая сами классы сущностей.

Аудит изменений данных вполне возможно делать в самой БД с помощью триггеров, но в базу не попадают сведения о том какой пользователь системы произвел изменения (в случае приложения, построенного по принципу trusted subsystem) и с какой целью были проведены эти изменения.

Для каждой операции работы изменения данных существует некоторый контекст, который включает в себя как некоторые явные параметры, как имя пользователя или URL, так и неявные, например намерения, с которыми были выполнены изменения данных (частично их можно восстановить по стеку вызовов).

Для задач логирования было бы удобно иметь доступ к некоторым параметрам контекста, особенно неявным. Эти параметры должны прозрачно передаваться по цепочке вызовов. Для решения таких задач можно применить монады, но тогда придется переписать весь код под использование монад, что очень проблематично. Можно воспользоваться возможностями AOP и IoC чтобы обеспечить неявную передачу явного контекста.

Для начала нам понадобится класс для хранения текущего контекста. Как и в случае со стеком вызовов, контексты тоже могут составлять некоторый стек.

public interface IContextManager<T>
{
    void Push(T value);
    void Revert();
    IEnumerable<T> GetValues();
}

Реализация такого интерфейса тривиальна:

public class ContextManager<T>: IContextManager<T>
{
    Stack<T> _stack = new Stack<T>();

    public void Push(T value)
    {
        _stack.Push(value);
    }

    public void Revert()
    {
        _stack.Pop();
    }

    public IEnumerable<T> GetValues()
    {
        foreach (var item in _stack)
        {
            yield return item;
        }
    }
}

 

Далее с помощью IoC контейнера создается один экземпляр этого класса, который живет в течение обработки одного запроса. Этот экземпляр инжектится в каждый класс, заинтересованный в установке или получении текущего контекста.

try
{
    _contextManager.Push(contextValue);
    //do actions
}
finally
{
    _contextManager.Revert();
}

Естественно каждый раз писать кучу try\finally не хочется, а учитывая что контекст в большинстве случаев статический, то хотелось бы задавать его декларативно и в одном месте.

Тут на помощь приходит AoP. Можно написать небольшой обработчик вызовов и с помощью Unity Interception устанавливать текущий контекст в зависимости от атрибута.

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class ContextAttribute : HandlerAttribute
{
    public ContextAttribute(object value)
    {
        this.Value = value;
    }

    public object Value { get; private set; }

    public override ICallHandler CreateHandler(IUnityContainer container)
    {
        var method = this.GetType().GetMethod("CreateHandlerInternal", BindingFlags.Instance | BindingFlags.NonPublic);
        return (ICallHandler)method.MakeGenericMethod(Value.GetType()).Invoke(this, new object[] { container });
    }

    private ContextCallHandler<T> CreateHandlerInternal<T>(IUnityContainer container)
    {
        return new ContextCallHandler<T>(container.Resolve<IContextManager<T>>(), (T)this.Value);
    }

}

public class ContextCallHandler<T>: ICallHandler
{
    IContextManager<T> _manager;
    T _value;

    public ContextCallHandler(IContextManager<T> manager, T value)
    {
        _manager = manager;
        _value = value;
    }

    public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
    {
        _manager.Push(_value);
        var result = getNext()(input, getNext);
        _manager.Revert();
        return result;
    }
    public int Order { get; set; }
}

 

Теперь навешивая атрибут [Context(value)] можно задавать контекст метода. А заинжектив в ObjectContext экземпляр типа IContextManager<T> можно при логировании получать текущее значение контекста, в случае если SaveChanges вызывается в самом конце цепочки вызовов.

Для того чтобы все работало надо правильно сконфигурировать контейнер как описано в этом посте.

Теги : IoC, Unity, .NET, AOP, Enterprise Library