Search code examples
c#dependency-injectioninversion-of-controlsimple-injector

How can i register an open generic type with a complex type constraint in Simple Injector?


I have an open generic type AccessMessageHandler<TProcess> which I want to resolve every time that an IProcessHandler<AccessMessage<TProcess>> is resolved. How can I do this?

This is my code:

public interface IProcess {}

public interface IProcessHandler<in TProcess> where TProcess : IProcess {
    void Handle(TProcess message);
}

public class AccessMessage<TProcess> : IProcess where TProcess : IProcess {
    public virtual string Username {get;protected set;}
    public virtual TProcess InnerProcess { get; protected set; }
}

public class AccessMessageHandler<TProcess> : IProcessHandler<AccessMessage<TProcess>> 
    where TProcess : IProcess {
    public AccessMessageHandler(IProcessHandler<TProcess> innerHandler){}
    public void Handle(AccessMessage<TProcess> message){
      // access control
      _innerHandler.Handle(message.InnerProcess)
    }
}

public class JustDoIt : IProcess {
    public virtual string What {get;set;}
}

public class JustDoItHandler : IProcessHandler<JustDoIt> {
    public void Handle(JustDoIt message) {
      // handle
    }
}

How can i register IProcessHandler, AccessMessageHandler for Simple Injector resolve like below:

var accessMessageProcess = new AccessMessage<JustDoIt>()
{ 
    Username = "user", 
    InnerProcess = new JustDoIt() { What="xxx" }
};

var handler  = GetHandlerFor(accessMessageProcess); 

// must return AccessMessageHandler<JustDoIt>(JustDoItHandler)
handler.Handle(accessMessageProcess);

Solution

  • You can do the following registration:

    container.RegisterManyForOpenGeneric(
        typeof(IProcessHandler<>), 
        typeof(JustDoItHandler).Assembly);
    
    container.RegisterOpenGeneric(
        typeof(IProcessHandler<>), 
        typeof(AccessMessageHandler<>));
    

    The call to RegisterManyForOpenGeneric will search the assembly of the JustDoItHandler and looks for all public concrete (non-generic) implementations of IProcessHandler<TProcess>. In the end this is just the same as doing a bunch of manual calls to container.Register<IProcessHandler<SomeProcess>, SomeProcessHandler>().

    The call to RegisterOpenGeneric maps an open generic abstraction to an open generic type. It uses unregistered type resolution on the background, so every time a IProcessHandler<TProcess> is requested that is not registered explicitly (using RegisterManyForOpenGeneric for instance), an AccessMessageHandler<TProcess> is resolved (if the generic type constraints match).

    The following code can be used to resolve the object graph and execute the handler:

    var handler = container.GetInstance<IProcessHandler<AccessMessage<JustDoIt>>>();
    
    handler.Handle(accessMessageProcess);
    

    This should resolve the following graph:

    IProcessHandler<AccessMessage<JustDoIt>> handler = 
        new AccessMessageHandler<JustDoIt>(
            new JustDoItHandler());
    

    Do note though that the AccessMessageHandler<TProcess> is not a decorator. A decorator wraps the same type as it implements, but your AccessMessageHandler<TProcess> implements IProcessHandler<AccessMessage<TProcess>> but wraps IProcessHandler<TProcess>. I think the right name for this pattern is a proxy.