Search code examples
c#dependency-injectioncastle-windsor

Resolving implementation of "Super interface" in Castle Windsor


Maybe not even possible but here's my need.
I have a generic implementation of a key mapper, and a Factory class capable of generating such mappers

public class KeyMapperFactory 
{
    IKeyMapper<TInternalKey, TExternalKey> GetMapper<TInternalKey, TExternalKey>(MapperConfig config)
}

Now I would like to have interfaces that "speak clear" in the application so I would create another empty interface in my app like this

public interface IMapCompanyToPayrollCompany : IKeyMapper<CompanyId, PayrollCompanyId>
{
}

Do I have any chances to create an "on the fly" implementation of IMapCompanyToPayrollCompany using CastleWindsor in something like this (wrong) way ?

var mapperConfig = MapperConfig {
...
};
var keyMapperFactory = new KeyMapperFactory();
var container = new WindsorContainer();

container.Register(
    Component
        .For<IMapCompanyToPayrollCompany>()
        .UsingFactoryMethod(kernel => (IMapCompanyToPayrollCompany)keyMapperFactory.GetMapper<CompanyId, PayrollCompanyId>(mapperConfig)
);

Solution

  • I found a solution using Castle's DynamicProxies

    // I developed an extension method on the IWindsorContainer
    public static void RegisterMapper<TSpeakingInterface, TInternal, TExternal>(this IWindsorContainer container, MapperConfig config) 
        where TSpeakingInterface : IKeyMapper<TInternal, TExternal>
    {
        container.Register(
            Component
                .For<TSpeakingInterface>()
                .UsingFactoryMethod(() => {
                    var generator = new ProxyGenerator(); // <--Documentation recommend this to be a Singleton for performance and memory reason ... 
                    var keyMapperFactory = new KeyMapperFactory();
                    var mapper = keyMapperFactory.GetMapper<TInternal, TExternal>(config);
                    var interceptor = new KeyMapperInterceptor<TInternal, TExternal>(mapper);
    
                    // see: https://github.com/castleproject/Windsor/issues/224
                    var nullProxy = generator.CreateInterfaceProxyWithoutTarget<TSpeakingInterface>();
                    return generator.CreateInterfaceProxyWithTarget(nullProxy, interceptor);
                })
        );
    }
    
    // Now I can register a mapper this way:
    var container = new WindsorContainer();
    var config = new MapperConfig {
        [...] // mapper config stuff here
    }
    container.RegisterMapper<IMapCompanyToPayrollCompany, CompanyId, PayrollCompanyId>(config);
    

    The interceptor is simple as this

    public class KeyMapperInterceptor<TInternal, TExternal> : IInterceptor
    {
       private readonly IKeyMapper<TInternal, TExternal> realMapper;
    
       public KeyMapperInterceptor(IKeyMapper<TInternal, TExternal> realMapper)
       {
           this.realMapper = realMapper;
       }
    
        public void Intercept(IInvocation invocation)
        {
           // We simply call the corresponding method on the realMapper
           var method = invocation.Method;
           invocation.ReturnValue = method.Invoke(realMapper, invocation.Arguments);
        }
    }
    

    ... and it works ! Of course additional methods or properties are not allowed in the IMapCompanyToPayrollCompany because the interceptor will try to execute/access them on the "realMapper" which does not know anything about!