2
votes

I'm experimenting with interception in Castle Windsor and notice that interceptors seem to be created as decorators of my service interface.

In other words, if I have an interface "ISomethingDoer" and a concrete "ConcreteSomethingDoer", the proxy implements ISomethingDoer but does not inherit from ConcreteSomethingDoer.

This is fine, and no doubt by design, but what I'm wondering is whether I can intercept protected virtual methods in my concrete classes that wouldn't be known by the public interface. I am doing this in order to add logging support, but I might want to log some of the specific internal details of a class.

In my slightly unimaginative test case I have this:

public interface ISomethingDoer
{
    void DoSomething(int Count);
}

[Loggable]
public class ConcreteSomethingDoer : ISomethingDoer
{
    public void DoSomething(int Count)
    {
        for (var A = 0; A < Count; A++)
        {
            DoThisThing(A);
        }
    }

    [Loggable]
    protected virtual void DoThisThing(int A)
    {
        ("Doing a thing with " + A.ToString()).Dump();
    }
}

So what I want to do is log calls to "DoThisThing" even though it's not part of the interface.

I've managed to get this working in Autofac. (I've created a Linqpad script here: http://share.linqpad.net/frn5a2.linq) but am struggling with Castle Windsor (see http://share.linqpad.net/wn7877.linq)

In both cases my interceptor is the same and looks like this:

public class Logger : IInterceptor
{
    public void Intercept(IInvocation Invocation)
    {
        String.Format("Calling method {0} on type {1} with parameters {2}",
            Invocation.Method.Name,
            Invocation.InvocationTarget.GetType().Name,
            String.Join(", ", Invocation.Arguments.Select(a => (a ?? "*null*").ToString()).ToArray())).Dump();
            Invocation.Proceed();
        "Done".Dump();
    }
} 

What I really want to do is say "any classes with a [Loggable] attribute, should use the logging interceptor". In the Autofac example I've specifically attached a logger to the registration, whereas with Castle I'm using an IModelInterceptorsSelector which looks like this:

public class LoggerInterceptorSelector : IModelInterceptorsSelector
{
    public bool HasInterceptors(ComponentModel Model)
    {
        return Model.Implementation.IsDefined(typeof(LoggableAttribute), true);
    }

    public InterceptorReference[] SelectInterceptors(ComponentModel Model, InterceptorReference[] Interceptors)
    {   
        return new[]
        {       
            InterceptorReference.ForType<Logger>()
        };
    }
}

Finally, the code to execute all this is:

    var Container = new WindsorContainer();

    Container.Register(
        Component.For<Logger>().LifeStyle.Transient
    );

    Container.Kernel.ProxyFactory.AddInterceptorSelector(new LoggerInterceptorSelector());

    Container.Register(
        Component.For<ISomethingDoer>()
        .ImplementedBy<ConcreteSomethingDoer>()
        .LifeStyle.Transient
    );

    var Doer = Container.Resolve<ISomethingDoer>();
    Doer.DoSomething(5);

When run I would expect to see "Calling method DoThisThing with parameters x" for each time the method is called. Instead I only get the call to DoSomething logged.

I can see why Castle Windsor is doing this, but I'm wondering if there is a way to tweak the behaviour?

(As a side-note I don't want to use Windsor's own interceptor attributes as I don't want to introduce dependencies to Castle outside of my composition root.)

I have tried resolving the ConcreteSomethingDoer specifically and this works, but not if I'm resolving the ISomethingDoer.

Apologies for the long post, and also apologies because I am pretty new to Castle Windsor!

1

1 Answers

1
votes

I you could register like:

Container.Register(
    Component.For<ISomethingDoer, ConcreteSomethingDoer>()
    .ImplementedBy<ConcreteSomethingDoer>()
    .LifeStyle.Transient
);

This should create a class proxy by deriving from ConcreteSomethingDoer. However this won't work with dynamic interceptors. However you probably can work around that by creating a facility which registers the interceptor when needed.