simpleinjector / SimpleInjector

An easy, flexible, and fast Dependency Injection library that promotes best practice to steer developers towards the pit of success.
https://simpleinjector.org
MIT License
1.21k stars 154 forks source link

How to restrict injected dependencies in context of the consuming type? #957

Closed PulsarFX closed 2 years ago

PulsarFX commented 2 years ago

This is a question on how to keep Client and Server code separated from each other with the help of SimpleInjector and not have creepy code undermine the code design.

For Requests we defined a Interface IOnlyWorkLocal like class OpenVisualStudioRequest : IOnlyWorkLocal, IRequest<Process> Requests with this interface should not be allowed to call the server.

Requests are registered with

container.Register<IMediator, Mediator>(Lifestyle.Singleton);
container.Register(typeof(IRequestHandler<,>), dllsToSearch);
container.Register(typeof(IRequestHandler<>), dllsToSearch);

For server calling we have the generic proxies:

container.RegisterConditional(typeof(IQueryHandler<,>), typeof(WebApiQueryHandler<,>), c => !c.Handled);
container.RegisterConditional(typeof(ICommandHandler<>), typeof(WebApiCommandHandler<>),c => !c.Handled);

My question is: Is there a way to have SimpleInjector detect and prevent the creation of RequestsHandlers which have a dependency on a WebApiQueryHandler (directly or indirectly)?
A candidate that should throw on .Verify() may look like this:

class OpenVisualStudioRequest : IOnlyWorkLocal { }

ctor:
public OpenVisualStudioRequestHandler(IQueryHandler<GetDatabaseStuffQuery> serverCaller) { }

Of course there may be queries which won't leave the client, which should not throw on .Verify():

class ALocalOnlyRequest : IOnlyWorkLocal { }

ctor:
public ALocalOnlyRequestHandler(IQueryHandler<GetLocalStuffQuery> localCaller) { }
dotnetjunkie commented 2 years ago

This can be done by letting the predicate of your RegisterConditional check for this. There are basically two options:

  1. You let the predicate return false, which causes Simple Injector to state that no suitable registration for IQueryHandler<GetLocalStuffQuery> was found.
  2. You let the predicate throw an exception in case it is injected into a consumer that does not match.

For instance:

container.RegisterConditional(typeof(IQueryHandler<>), typeof(WebApiQueryHandler<>), c =>
{
    if (c.Handled) return false;

    var consumer = c.Consumer.ImplementationType;
    var handlerTypes = consumer.GetClosedTypesOf(typeof(IRequestHandler<,>))
        .Concat(consumer.GetClosedTypesOf(typeof(IRequestHandler<>)));

    var requestMessageTypes = handlerTypes.Select(type => type.GetGenericArguments().First());
    var onlyLocalMessageTypes = requestMessageTypes.Where(typeof(IOnlyWorkLocal).IsAssignableFrom);

    return !onlyLocalMessageTypes.Any();
    // or do  if (onlyLocalMessageTypes.Any()) throw new ArchitecturalRulesViolatedException(); return true;
});
PulsarFX commented 2 years ago

This is a very good start, thanks alot. 👍

PulsarFX commented 2 years ago

for reference: I had to insert

if (!c.HasConsumer)  { return true; }

before accessing c.Consumer to prevent InvalidOperationExceptions