Search code examples
c#-4.0inversion-of-controldesign-patternsioc-containerservice-locator

LightCore ServiceLocator with multiple registration on the same contract


We try to integrate LightCore as the default Service Locator in our Metadata- / ORM-Framework. Therefore we like to to some default Registrations from within the Framework that a Framework-User (=Application Developer) can "overrule" somehow with his own implementations (if he likes to do this at all). How should this be done with the LightCore IoC containers or other IoC containers?

What we tried:

var builder = new ContainerBuilder();
builder.Register<Foo>().ControlledBy<SingletonLifecycle>();
builder.Register<Foo, Foo2>().ControlledBy<SingletonLifecycle>();

var container = builder.Build();

var foo = container.Resolve<Foo>();

We registered two classes as contract for Foo. With the above code we always get the the first (instances of Foo) returned. So no overruling here. Btw: we like to get an instance of Foo2.

We changed it from using concrete classes to using interfaces:

var builder = new ContainerBuilder();
builder.Register<IFoo, Foo>().ControlledBy<SingletonLifecycle>();
builder.Register<IFoo, Foo2>().ControlledBy<SingletonLifecycle>();

var container = builder.Build();

var foo = container.Resolve<IFoo>();

With this in place we get a resolve exceptionn in Resolve<>() tell that no registration could be found. If we remove the second "Register()" statement it works in the way we get an instance of Foo.

We're not sure if we missed some general concepts. Does this work with other IoC's the same way? What is the recommended way to overrule / override registrations?

Any help on this topic would be great - not only for LightCore.

Update: I've set up some tests for the above scenario using the SimpleInjector IoC container. With this container one needs to specify AllowOverridingRegistration = true in the constructor and it will work as expected. So it looks like LightCore simply doesn't support this use-case correctly but others do.

Update: We got a fast response from the LightCore creator saying that LigtCore does not support overriding registrations at all. So there seems to be no way address these scenarios registration overriding with LightCore so we switched from LightCore to SimpleInjector.

The following SimpleInjector configuration matches our four current requirements:

  // Register concrete class for FooFoo
  container.RegisterSingle<FooFoo>();

  // Register concrete classes for Foo - Final registration should return FooFoo, not Foo
  container.RegisterSingle<Foo>();
  container.RegisterSingle<Foo, FooFoo>();

  // Register interfaces for IFoo - Final registration should return FooFoo, not Foo
  container.RegisterSingle<IFoo, Foo>();
  container.RegisterSingle<IFoo, FooFoo>();

  // Register list of Plugs
  container.RegisterAll(new IPlug[] { new PlugA(), new PlugB() });

Cheers, Marc


Solution

  • AFAIK all the big DI frameworks (Autofac, Unity, Castle Windsor, StructureMap, Ninject) have features for overriding registrations. Most of them allow this by doing multiple registrations for the same service type and they pick one of the registrations as the default one. Which instance they pick however, differs per framework. While some frameworks pick the first out of multiple registrations, others pick the last registration. Knowing how the framework of choice does this is important, since this determines whether you should either register the 'overriding' instance before, or after all others.

    Because all those frameworks have a different overload resolution, I decided to let the Simple Injector don't have any overriding behavior by default (although it is configurable, as you already noticed). This makes it easier to migrate to another framework, which was one of the design goals of the Simple Injector. Allowing registrations to be overridden by default however, can also be a source of configuration mistakes, which can be time consuming to track down. Since Simple Injector should be easy to start with, it made sense to disallow overriding (by default).

    With Simple Injector, when AllowOverridingRegistration is enabled, a new registration really replaces an earlier registration for that same type. This differs from the other containers, that usually keep the other registrations, and allow you to resolve all those registrations as a single collection. With Simple Injector, a collection of things must be registered separately and can be overridden separately.

    About all other containers besides Simple Injector and the big ones I can't say much. However, I think that overriding will be hard to do with most of them.