Jamie Peacock
Jamie Peacock

Reputation: 372

How to Inject multiple Implementations of an Interface into one Implementation

I am trying to inject a collection of implementations of IResourceService into another implementation of IResourceService so that the first implementation is able to redirect to the correct service in the case where it can't operate on the resource. Every implementation of this I have tried so far has lead to a Cyclic Dependency - so I either want to know how to work around this or how to design this so that it isn't a problem.

I'm creating a number of controllers that all look after different resources. I'll simplify for the case of this question - but in effect I have a PetController that is able to do simple operations on pets which are all provided Ids. So for example navigating to /pet/1234 will give you a summary of the pet's details and what type of pet it is.

I then have a number of controllers for each pet type I.e. DogController, CatController and HamsterController. All of these inherit from a BasePetController which is actually doing all of the heavy lifting. In the scenario where you perform an operation on /dog/1234 but PetId 1234 actually corresponds to a Hamster, I want the DogController to return an appropriate response (Redirect on a Get, Confict on an update etc).

So I have something like this:

BasePetController : IResourceController
{
    BasePetController(IResourceController[] resourceControllers){}

    abstract Type SupportedResourceType { get; }
}

DogController : BasePetController
{
     DogController(IResourceController[] resourceControllers) : base(resourceControllers) {}

     Type SupportedResourceType => typeof(Dog);
}

This is then added to DI like so:

services.AddSingleton<IResourceController, DogController>();
services.AddSingleton<IResourceController, CatController>(); // etc.

This leads to a cyclic dependency because the DogController needs itself in order to create itself effectively.

I have tried to inject a ResourceControllerSelector to separate out this conflict a little further but no dice.

What I want is for the controller to go (In Pseudo Code)

If (can't update dog because it isn't a dog) {
    controller = getResourceControllerForPet()
    action = controller.GetActionForUpdateOperation()
    return information for the user to go to the correct action
}

I think the individual pet controllers should be responsible for knowing whether or not that can handle a specific resource - rather than another service knowing all of this information + which actions to call etc. But can't figure out how to give each pet controller the ability to go and get this information themselves and/or in the API layer without creating a cyclic dependency.

Any help on this would be much appreciated. Whether its help with the dependency injection problem, or the design of the services required or even the API itself.

Adding implementation of the ResourceControllerSelector where resourceControllers is an IEnumerable<IResourceController>.

public IResourceController SelectController(Type resourceType)
{
    EnsureArg.IsNotNull(resourceType, nameof(resourceType));

    return this.resourceControllers.SingleOrDefault(x => x.SupportedResourceType == resourceType)
        ?? throw new ArgumentOutOfRangeException(nameof(resourceType), resourceType, "No resource controller has been configured to handle the provided resource.");
}

Upvotes: 1

Views: 1225

Answers (2)

Jamie Peacock
Jamie Peacock

Reputation: 372

I think this question has diverged, there is the question itself (which was my first attempt at solving the problem I had) and then the problem I had. So I thought I'd update the question with what I have learned.

Answering the question

Short answer is this isn't possible with the ASP.Net standard dependency injection, it isn't advanced enough. However with things like Autofac and/or SimpleInjector you can do conditional injection or context driven injection. In this world you can say inject all Interface implementations that don't meet a certain criteria. So you can say don't inject if the implementation matches the on you are trying to create at the moment. I haven't tried this in anger but in theory this would get around the problem

Solving my problem

The reason I wanted this was so that I could return accurate SeeOther (303) responses if you tried to perform an action using the DogController on a Hamster. In my mind that meant injecting the other controllers so that it could query them for information (I.e. find the actionName and controllerName of the correct action). However another way is to use reflection and attributes to denote what the controller is responsible for. So I instead went with something like this:

I decorate each resource controller like so: [ResourceController(Type = typeof(Dog), ControllerName = "Dog")] public class DogController : BasePetController

And then I have a helper class here called SeeOtherResultResolver that scans the repository for the correct controller and then for the right HttpGet or HttpDelete etc attribute.

private static readonly IDictionary<string, Type> AttributeMap
     = new Dictionary<string, Type>
     {
         { "GET", typeof(HttpGetAttribute) },
         { "DELETE", typeof(HttpDeleteAttribute) },
         { "PUT", typeof(HttpPutAttribute) },
     };

public string ResolveForSeeOtherResult(IUrlHelper urlHelper, object resource, object routeValues = null)
{
    string scheme = urlHelper.ActionContext.HttpContext.Request.Scheme;
    var httpAttribute = AttributeMap[urlHelper.ActionContext.HttpContext.Request.Method];

    var resourceControllerTypes = resource.GetType().Assembly.GetTypes()
            .Where(type => type.IsDefined(typeof(ResourceControllerAttribute), false));

    foreach (var type in resourceControllerTypes)
    {
        var resourceControllerAttribute = type.GetCustomAttributes(false).OfType<ResourceControllerAttribute>().Single();

        if (resourceControllerAttribute.Value == resource.GetType())
        {
            var methodInfo = type.GetMethods().Where(x => x.IsDefined(httpAttribute, false)).Single();

            var result = urlHelper.Action(methodInfo.Name, resourceControllerAttribute.ControllerName, routeValues, scheme);

            return result;
         }
    }

    var errorMessage = string.Format(ErrorMessages.UnrecognisedResourceType, resource.GetType());

    throw new InvalidOperationException(errorMessage);
}

Thanks for the help everyone. I really do appreciate it, you made me realise I was barking up the wrong tree a little bit!

Upvotes: 1

Paweł G&#243;rszczak
Paweł G&#243;rszczak

Reputation: 574

i think i get your problem.

The solution is simple just create stubbed interfaces for each pet concrete type so it will look like this. And each controller will use its own resource.

public DogController {
public DogController(IDogResource dogResource)
}

public CatController {
   public CatController(ICatResource dogResource)
}

public interface IDogResource : IResource
{
}

public interface ICatResource : IResource
{
}

public interface IResource{
  //your common logic
}

services.AddSingleton<DogResource, IDogResource>();
services.AddSingleton<CatResource, ICatResource>();

**edit in case, if you have some common logic in base controller you need to modify above code to this form and the rest is same

public BasePetController  {
  protected IResource resource;
  public BasePetController (IResource resource)
  {
      this.resource = resource;
  }
}

public DogController : BasePetController 
{
  public DogController(IDogResource dogResource): base (dogResource)
}

public CatController  : BasePetController
{
  public CatController(ICatResource dogResource): base (dogResource)
}

** one more edit

Thanks for the response but this isn't quite what I was looking for - what I effectively want here is to inject ICatResource and IHamsterResource into the DogController and the Dog+Hamster into the Cat one etc. But I wanted that done automatically (in theory it doesn't matter if the Dog one gets itself)

So if you want do to smth like this just take the above code and change it. Dont use concrete interface types like IDogResource etc, only use IResource, and the code will look like this

public class BasePetController  {
  protected IResource[] resources;
  public BasePetController (IResource[] resources)
  {
    this.resources = resources;
  }
}

public class DogController : BasePetController 
{
  public DogController(IResource[] resources): base (resources)
}

public class CatController  : BasePetController
{
  public CatController(IResource[] resources): base (resources)
}

public class DogResource : IResource 
{

}
public class DogResource : IResource 
{

}
public interface IResource{

}

services.AddSingleton<DogResource, IResource>();
services.AddSingleton<CatResource, IResource>();

Upvotes: 0

Related Questions