Search code examples
c#dependency-injectionsimple-injector

SimpleInjector: batch registration of classes with multiple interfaces


Let's say I have the following classes that implement the following interfaces:

class MyClassA : IInterface<MyClassA>, IInterface
class MyClassB : IInterface<MyClassB>, IInterface

I have two other classes like the following:

class ImportA {
    public ImportA(IEnumerable<IInterface>) {}
}

class ImportB {
    public ImportB(IInterface<MyClassA>) {}
}
  • I want ImportA to be injected with every class that implements IInterface in my assembly.
  • I want ImportB to be injected with just an instance of IInterface<MyClassA>.
  • I want the instance of concrete class MyClassA from both methods to be the same (LifeTime: Singleton).

I can't figure out how to get this to work with Simple Injector. I've tried RegisterCollection and https://simpleinjector.readthedocs.org/en/latest/howto.html#register-multiple-interfaces-with-the-same-implementation

The closest I have come is getting an error saying one class imported as Singleton the other as Transient.

I've tried changing all the LifeStyles to Singleton and setting the config to Singleton by default, to no avail.

UPDATE 1: I am looking for an automated approach. I have a lot of classes to register and don't want to have to do it by hand. Here is a different sample. The below registration examples didn't work. The automated one failed with:

An exception of type 'SimpleInjector.ActivationException' occurred in  
SimpleInjector.dll but was not handled in user code

Additional information: The constructor of type MyClassB contains the 
parameter with name 'myClassA' and type IInterface<MyClassA> that is not 
registered. Please ensure IInterface<MyClassA> is registered, or change the 
constructor of MyClassB. There is, however, a registration for 
IEnumerable<IInterface<MyClassA>>; Did you mean to depend on 
IEnumerable<IInterface<MyClassA>>?

Here is my current code:

public interface IInterface {
  string Value { get; set; }
}

public interface IInterface<T> : IInterface { }

public class MyClassA : IInterface<MyClassA> {
  public string Value { get; set; }
}

public class MyClassB : IInterface<MyClassB> {
  private IInterface<MyClassA> myClassA;

  public MyClassB(IInterface<MyClassA> myClassA) {
    this.myClassA = myClassA;
  }

  public string Value {
    get { return this.myClassA.Value; }
    set { this.myClassA.Value = value; }
  }
}

public class ImportA {
  public ImportA(IEnumerable<IInterface> imports) {
    // fails on this line
    var a = imports.SingleOrDefault(i => i is IInterface<MyClassA>);
    a.Value = "test";

    var b = imports.SingleOrDefault(i => i is IInterface<MyClassB>);

    // should output the value of "test";
    Console.WriteLine(b.Value);
    Console.ReadKey();
  }
}

...
static void Main() {
  var container = new Container();

  var types = container.GetTypesToRegister(
    typeof(IInterface<>), new[] { typeof(IInterface<>).Assembly });

  var registrations = (
    from type in types
    select Lifestyle.Singleton.CreateRegistration(type, type, container))
    .ToArray();

  container.RegisterCollection<IInterface>(registrations);
  container.RegisterCollection(typeof(IInterface<>), registrations);

  var a = container.GetInstance(typeof(ImportA));
}

ImportA is the entry point and should be Transient. Everything else should be singleton so that when MyClassA is modified in ImportA the value is picked up in MyClassB.


Solution

  • The way to do this is by creating Registration instances manually and use them to register two separate collections. Example:

    var a = Lifestyle.Singleton.CreateRegistration<MyClassA>(container);
    var b = Lifestyle.Singleton.CreateRegistration<MyClassB>(container);
    
    container.RegisterCollection<IInterface>(new[] { a, b });
    
    container.AddRegistration(typeof(IInterface<MyClassA>), a);
    container.AddRegistration(typeof(IInterface<MyClassB>), b);
    

    This will ensure that the same single instance is used in both collections. If a class implements multiple generic versions of IInterface<T>, that same single instance will even be reused over the different collections for IInterface<T>.

    If you want a more automated approach, you can do this:

    var types = container.GetTypesToRegister(typeof(IInterface<>), assemblies);
    
    // NOTE: The ToArray() call is crucial to prevent torn lifestyles.
    var registrations = (
        from type in types
        select Lifestyle.Singleton.CreateRegistration(type, type, container))
        .ToArray();
    
    container.RegisterCollection<IInterface>(registrations);
    
    foreach (var registration in registrations) {
        container.AddRegistration(
            typeof(IInterface<>).MakeGenericType(registration.ImplementationType),
            registration);
    }