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);
If the information about the current user is the only information you wish to pass on, you might be better of hiding this user information behind an abstraction. This way you might be able to prevent having a specific
IProcessHandler<AccessMessage<TProcess>>
interface that consumers need to use and you prevent having to pass on the username from every consumer (this is extra code that can be forgotten and extra code to unit test for).If you hide this information behind an abstraction, you prevent having to extend the
IProcessHandler<TProcess>
interface to anIProcessHandler<AccessMessage<TProcess>>
interface and this allows you to use a decorator instead:So instead of having to depend on
IProcessHandler<AccessMessage<JustDoIt>>
, a consumer can simply depend onIProcessHandler<JustDoIt>
. Of course you need to create anIPrincipal
implementation that is able to supply its consumers with the name of the current user, but this will be a no-brainer in most cases, becauseIPrincipal
is part of the .NET framework, and in most cases (for instance when running an ASP.NET application), you can simply get an instance for the current user by callingThread.CurrentPrincipal
. In other words, you can register theIPrincipal
as follows:The
AccessProcessHandlerDecorator<TProcess>
can be registered as follows:By registering this decorator you are telling Simple Injector to automatically wrap every returned
IProcessHandler<TProcess>
with anAccessProcessHandlerDecorator<TProcess>
. This allows you to add cross-cutting concerns to your application without having to change the consuming code.UPDATE
Simple Injector understands all generic type constraints that you apply to open generic types such as decorators. If you only want to apply this decorator where
TProcess
is anAccessMessage<IProcess>
, you can apply the predicate as @qujck states in his comment, or you can again add a generic type constraint to your decorator. The decorator will in that case look pretty much like theAccessMessageHandler<TProcess>
that you defined in your question:When you make the following registration:
The
AccessProcessHandlerDecorator<T>
will only be applied to anyIProcessHandler<AccessMessage<TProcess>>
and not to anything else.You can do the following registration:
The call to
RegisterManyForOpenGeneric
will search the assembly of theJustDoItHandler
and looks for all public concrete (non-generic) implementations ofIProcessHandler<TProcess>
. In the end this is just the same as doing a bunch of manual calls tocontainer.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 aIProcessHandler<TProcess>
is requested that is not registered explicitly (usingRegisterManyForOpenGeneric
for instance), anAccessMessageHandler<TProcess>
is resolved (if the generic type constraints match).The following code can be used to resolve the object graph and execute the handler:
This should resolve the following graph:
Do note though that the
AccessMessageHandler<TProcess>
is not a decorator. A decorator wraps the same type as it implements, but yourAccessMessageHandler<TProcess>
implementsIProcessHandler<AccessMessage<TProcess>>
but wrapsIProcessHandler<TProcess>
. I think the right name for this pattern is a proxy.