How can i register an open generic type with a com

2019-06-04 01:09发布

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);

2条回答
ゆ 、 Hurt°
2楼-- · 2019-06-04 01:49

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 an IProcessHandler<AccessMessage<TProcess>> interface and this allows you to use a decorator instead:

public class AccessProcessHandlerDecorator<TProcess> : IProcessHandler<TProcess> {
    private readonly IProcessHandler<TProcess> decoratee;
    private readonly IPrincipal userContext;

    public AccessProcessHandlerDecorator(IProcessHandler<TProcess> decoratee,
        IPrincipal userContext) {
        this.decoratee = decoratee;
        this.userContext = userContext;
    }

    public void Handle(TProcess process) {
        string user = this.userContext.Identity.Name;

        // access control

        this.decoratee.Handle(process);
    }
}

So instead of having to depend on IProcessHandler<AccessMessage<JustDoIt>>, a consumer can simply depend on IProcessHandler<JustDoIt>. Of course you need to create an IPrincipal 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, because IPrincipal 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 calling Thread.CurrentPrincipal. In other words, you can register the IPrincipal as follows:

container.Register<IPrincipal>(() => Thread.CurrentPrincipal);

The AccessProcessHandlerDecorator<TProcess> can be registered as follows:

container.RegisterDecorator(typeof(IProcessHandler<>), 
    typeof(AccessProcessHandlerDecorator<>));

By registering this decorator you are telling Simple Injector to automatically wrap every returned IProcessHandler<TProcess> with an AccessProcessHandlerDecorator<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 an AccessMessage<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 the AccessMessageHandler<TProcess> that you defined in your question:

public class AccessProcessHandlerDecorator<TProcess> 
    : IProcessHandler<AccessMessage<TProcess>>
{
    private readonly IProcessHandler<AccessMessage<TProcess>> decoratee;
    private readonly IPrincipal userContext;

    public AccessProcessHandlerDecorator(
        IProcessHandler<AccessMessage<TProcess>> decoratee,
        IPrincipal userContext) {
        this.decoratee = decoratee;
        this.userContext = userContext;
    }

    public void Handle(AccessMessage<TProcess> process) {
        string user = this.userContext.Identity.Name;

        // access control

        this.decoratee.Handle(process);
    }
}

When you make the following registration:

container.RegisterDecorator(typeof(IProcessHandler<>), 
    typeof(AccessProcessHandlerDecorator<>));

The AccessProcessHandlerDecorator<T> will only be applied to any IProcessHandler<AccessMessage<TProcess>> and not to anything else.

查看更多
混吃等死
3楼-- · 2019-06-04 01:50

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.

查看更多
登录 后发表回答