Search code examples
c#dependency-injectionninjectsimple-injectorninject.web.mvc

How to use Ninject's Named bindings in Simple Injector?


I have an MVC application using Ninject for DI and I'm planning to migrate the same to Simple Injector. But I couldn't find any alternatives for replacing the named binding of Ninject with Simple Injector.

Binding:

    kernel.Bind<IStudent>().To<Student1>().Named("Senior");
    kernel.Bind<IStudent>().To<Student2>().Named("Junior");
    kernel.Bind<IStudent>().To<Student3>().Named("Junior");

Controller:

public class StudentController
{
  private readonly IEnumerable<IStudent> _seniors;
  private readonly IEnumerable<IStudent> _juniors;
  public StudentController([named("Senior")] IEnumerable<IStudent> seniors,[named("Junior")] IEnumerable<IStudent> juniors)
  {
    _seniors=seniors;
    _juniors=juniors;
  }
}

I referred few links How to use Ninject's Named bindings in Simple Injector. But didn't have any luck.


Solution

  • There are many options:

    Option 1: Using the [named] attribute

    To achieve this, you will have to recreate the NamedAttribute, since this is not something that exists in Simple Injector, for good reason.

    You can make the following two conditional registrations for collections of students:

    container.RegisterConditional<IEnumerable<IStudent>>(
        container.Collection.CreateRegistration<IStudent>(
            typeof(Student1)),
        c => c.Consumer.Target.GetCustomAttribute<namedAttribute>()?.Name == "Senior");
    
    container.RegisterConditional<IEnumerable<IStudent>>(
        container.Collection.CreateRegistration<IStudent>(
            typeof(Student2),
            typeof(Student3)),
        c => c.Consumer.Target.GetCustomAttribute<namedAttribute>()?.Name == "Junior");
    

    Each conditional registration wraps a Registration for an IStudent collection. The predicate filters the target, in this case the constructor argument, for the name of its namedAttribute.

    Option 2: Without the named attribute, by checking the argument name

    A simpler option, however, is to ditch the named attribute alltogether, and just filter based on the name of the constructor argument:

    container.RegisterConditional<IEnumerable<IStudent>>(
        container.Collection.CreateRegistration<IStudent>(
            typeof(Student1)),
        c => c.Consumer.Target.Name == "seniors");
    
    container.RegisterConditional<IEnumerable<IStudent>>(
        container.Collection.CreateRegistration<IStudent>(
            typeof(Student2),
            typeof(Student3)),
        c => c.Consumer.Target.Name == "juniors");
    

    This registration is almost the same as with option 1, but now we filter based on the actual name of the argument, instead of based on its attribute.

    Option 3: By manually wiring StudentsController.

    An even simpler option is to revert from Auto-Wiring StudentsController to wiring it manually, as follows:

    var seniors = container.Collection.Create<IStudent>(typeof(Student1));
    var juniors = container.Collection.Create<IStudent>(typeof(Student2), typeof(Student3));
    container.Register(() => new StudentController(seniors: seniors, juniors: juniors));
    

    Here we request the Container to create the two collection of students, and create the registration for StudentsController where both collections are injected into.

    Do note that in Simple Injector collections are streams. This means that the call to Collection.Create does not create the student instances, just a reference to a stream that will produce student instances when iterated. This means, the stream can be created at application startup, while the registrations lifestyles are preserved.

    Also note that if you're calling RegisterMvcControllers, you will have to override the existing registration for this controller class. This page shows how to do this.

    Option 4: Changing the design. Make 'Level' a property of IStudent. Filter inside the controller.

    You can decide to change your design in a way that registration is simplified. Whether this is better highly depends on the context, so please take this with a grain of salt. However, when you add a Level property, or something similar, to the IStudent interface that represents whether a student is a senior, and filter based on that inside the controller, your registrations are very much reduced to the following:

    container.Collection.Register<IStudent>(typeof(Student1).Assembly);
    

    Here we use Auto-Registration to search for all student implementations and register them all into one single collection.

    In this case the StudentController will be left with a single constructor parameter, but you obviously moved the responsibility for filtering out of the Composition Root, into the controller.

    public class StudentController
    {
        private readonly IEnumerable<IStudent> _students;
    
        public StudentController(IEnumerable<IStudent> students)
        {
            _students = students;
        }
    }