Search code examples
c#autofaccastle-windsor

How to keep interceptors related to each other?


I created some interceptors for request logging, response logging and exception with autofac and castle. Some methods can have request logs and some response logs or both. I want to wire up requests with exceptions and responses.

I mean I want to pass request id to other interceptors. I don't want to keep all of them in one interceptor. Is there a way to get unique id from invocation or shared variable on same invocation?

Let's check code below, I want to access same id to wire before and after logs.

public class BeforeAspect : IInterceptor
{
    public BeforeAspect()
    {

    }
    public void Intercept(IInvocation invocation)
    {
        System.Diagnostics.Debug.WriteLine("some id on before");

        invocation.Proceed();
    }
}
public class AfterAspect : IInterceptor
{
    public AfterAspect()
    {

    }
    public void Intercept(IInvocation invocation)
    {
        invocation.Proceed();

        System.Diagnostics.Debug.WriteLine("exact id like before");
    }
}

Thanks in advance.


Solution

  • To get a unique identifier for the actual request you can introduce a new service that will provide this identifier.

    For example let's call it IContextIdentifier

    public interfac IContextIdentifier
    {
        String Identifer { get; }
    }
    

    And

    public class BeforeAspect : IInterceptor
    {
        public BeforeAspect(IContextIdentifier contextIdentifier)
        {
            this._contextIdentifier = contextIdentifier; 
        }
    
        private readonly IContextIdentifier _contextIdentifier; 
    
    
        public void Intercept(IInvocation invocation)
        {
            System.Diagnostics.Debug.WriteLine($"id is {this._contextIdentifier.Identifier}");
    
            invocation.Proceed();
        }
    }
    

    the concrete implementation of IContextIdentifier could be as simple as :

    public class SimpleContextIdentifier : IContextIdentifier 
    {
        public SimpleContextIdentifier()
        {
            this.Identifier = Guid.NewGuid().ToString(); 
        }
        public String Identifier { get; private set; }
    }
    

    Then you register the ContextIdentifier as InstancePerLifetimeScope to have a unique instance per request.

    builder.RegisterType<SimpleContextIdentifier>()
           .As<IContextIDentifier>()
           .InstancePerLifetimeScope();
    

    You can also use the HttpContext.TraceIdentifier property if you have access to an HttpContext; HttpContext

    public class HttpContextIdentifier : IContextIdentifier 
    {
        public SimpleContextIdentifier(IHttpContextAccessor httpContextAccessor)
        {
            this._httpContextAccessor = httpContextAccessor; 
        }
    
        private readonly IHttpContextAccessor _httpContextAccessor;
    
        public String Identifier => this._httpContextAccsor.TraceIfentifier; 
    }
    

    As far as I know there is no built-in solution to obtain the current method call identifier using Castle.Core interception framework. But you can use ConditionalWeakTable on IInvocation instance to get a unique identifier.

    public class MethodCallIdentifier<T> : IMethodCallIdentifier
        where T : class
    {
    
        private class Key
        {
            public Key()
            {
                this.Id = Guid.NewGuid();
            }
            public Guid Id { get; private set; }
        }
    
        public MethodCallIdentifier()
        {
            this._identifiers = new ConditionalWeakTable<T, Key>();
        }
    
        private readonly ConditionalWeakTable<T, Key> _identifiers;
    
        public Guid GetUniqueIdentifier(T obj)
        {
            Key key = this._identifiers.GetOrCreateValue(obj);
            return key.Id;
        }
    }
    

    you should register MethodCallIdentifier as single instance

    builder.RegisterGeneric(typeof(MethodCallIdentifier<>))
           .As(typeof(IMethodCallIdentifier<>))
           .SingleInstance;
    

    and then your interceptors can have a dependency on IMethodCallIdentifier<IInvocation>

    I have not tried this solution, but it should work.