Search code examples
c#dependency-injectionsimple-injector

How can get instance of specific namespace with SimpleInjector?


public class IMessageHandler<T> { 
    void Handle(T message);
}

I have multiple IMessageHandler implementation of same type in different namespaces.

Context1Namesapce
  Handler1.cs
  Handler2.cs
  Handler3.cs
Context2Namesapce
  Handler1.cs
  Handler2.cs
CompositeMessageHandler.cs

I need handlers that belong to specific namespace when resolving instance

As example, below code should return CompositeMessageHandler only includes Handlers in Context1Namespace.

 Type handlerType = typeof(IMessageHandler<>).MakeGenericType(message.GetType());
 dynamic handler = _container.GetInstance(handlerType,"Context1Namespace");

How can i implement this logic with simpleinjector?

Alternative solution: I can filter resolved instances after GetInsance call, but I have multiple decorator handlers top of the CompositeMessageHandler so this solution leads to problems.


Solution

  • There are two solutions that I can think of that would work in your scenario. Either you manually create InstanceProducer instances and filter on them to get the proper handlers to execute, or you make use of some ambient value and apply a decorator that decides whether or not to execute such handler. Here are examples of both:

    Manually Creating InstanceProducers:

    // During registration
    var handlerProducerInfos = (
        from type in container.GetTypesToRegister(typeof(IMessageHandler<>), assemblies)
        let registration = Lifestyle.Transient.CreateRegistration(type, container)
        from service in type.GetClosedInterfacesFor(typeof(IMessageHandler<>))
        let producer = new InstanceProducer(service, registration)
        select new
        {
            Producer = new InstanceProducer(service, registration),
            Namespace = type.Namespace
        })
        .ToArray();
    
    // During execution
    Type handlerType = typeof(IMessageHandler<>).MakeGenericType(message.GetType());
    
    var handlers =
        from info in handlerProducerInfos
        where handlerType == info.Producer.ServiceType
        where info.Namespace == "Context1Namespace"
        select info.Producer.GetInstance();
    
    foreach (dynamic handler in handlers) {
    
    }
    

    Using an ambient value and a special decorator:

    Here we can use the CompositeMessageHandler<T> as usual:

    public class CompositeMessageHandler<T> : IMessageHandler<T> {
        private readonly IEnumerable<IMessageHandler<T>> handlers;
        public CompositeMessageHandler(IEnumerable<IMessageHandler<T>> handlers) {
            this.handlers = handlers;
        }
    
        public void Handle(T message) {
            foreach (var handler in this.handlers) {
                handler.Handle(message);
            }
        }
    }
    

    And we have a special decorator that takes a dependency on Simple Injector's DecoratorContext object. Simple Injector can inject this class into your decorator and it gives your decorator more information about the context it is running in:

    public class ContextualMessageHandlerDecorator<T> : IMessageHandler<T> {
        private readonly DecoratorContext context;
        private readonly IMessageHandler<T> decoratee;
    
        public ContextualMessageHandlerDecorator(DecoratorContext context,
            IMessageHandler<T> decoratee) {
            this.context = context;
            this.decoratee = decoratee;
        }
    
        public static string ContextNamespace { get; set; }
    
        public void Handle(T message) {
            // Here we get the ambient value from the ContextHelper and match it
            // with the namespace of the real message handler
            if (ContextHelper.ContextNamespace.Value.Equals(
                this.context.ImplementationType.Namespace)) {
                this.decoratee.Handle(message);
            }
        }
    }
    
    public static ContextHelper {
        public static readonly ThreadLocal<string> ContextNamespace =new ThreadLocal<string>();
    }
    

    We can register everything as follows:

    // Register the concrete message handlers as collection
    container.RegisterCollection(typeof(IMessageHandler<>), assemblies);
    
    // Register the composite to wrap the real message handlers
    container.Register(typeof(IMessageHandler<>), typeof(CompositeMessageHandler<>));
    
    // Register your decorators here:
    
    // Register our 'contextual' decorator last, but prevent it to be wrapped 
    // around the CompositeMessageHandler.
    container.RegisterDecorator(typeof(IMessageHandler<>), 
        typeof(ContextualMessageHandlerDecorator<>),
        c => !c.ImplementationType.Name.StartsWith("CompositeMessageHandler"));
    

    During execution:

    Type handlerType = typeof(IMessageHandler<>).MakeGenericType(message.GetType());
    // Resolves the composite handler, wrapping all handlers for T.
    dynamic handler = _container.GetInstance(handlerType);
    // Set the context to allow filtering on namespace
    ContextHelper.ContextNamespace.Value = "Context1Namespace";
    // handle the message
    handler.Handle((dynamic)message);