Is it possible and/or a good idea to use Ninject (or any other IoC container, for that matter) to create a default binding for situations in which an appropriate implementation does not exist, and use this default binding rather than have to handle an ActivationException
when either multiple bindings exist, or no bindings exist for a particular request?
I've been using Ninject's Factory and Conventions extension projects, but I'm wondering if they're masking a mistake I'm making at a more fundamental level, so I've created a test to illustrate what I want to do, as simply as I can:
Given the following:
public interface IWidget { }
public class DefaultWidget : IWidget { }
public class BlueWidget : IWidget { }
And the following xUnit test using FluentAssertions:
[Fact]
public void Unknown_Type_Names_Resolve_To_A_Default_Type()
{
StandardKernel kernel = new StandardKernel();
// intention: resolve a `DefaultWidget` implementation whenever the
// 'name' parameter does not match the name of any other bound implementation
kernel.Bind<IWidget>().To<DefaultWidget>();
kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof(BlueWidget).Name);
kernel.Get<IWidget>("RedWidget").Should().BeOfType<DefaultWidget>();
// ACTIVATION EXCEPTION (**NO matching bindings available** for `IWidget`)
}
I'm not sure exactly how going down this road, if it's even possible, would lead me to an unsavory implementation of the Service Locator pattern, other than it seems to be a warning given by those who have answered similar questions.
So, is this an abuse/misuse of IoC containers to do what I am asking it to do?
It seems like I should want to bind/resolve types with an IoC for all the explicit types I do have, so that part doesn't seem wrong to me. In real life, there would be many more explicit bindings like BlueWidget
, as well as an unknown number of variations on the "RedWidget" string value coming in.
Generally speaking, it seems like the notion of a default implementation of some interface is a not-so-uncommon situation, so where would this mechanism for resolving requests be, if not within the IoC container's realm?
I also plan on using the factory pattern to create IWidget
implementations. So far, I've been using the factories created automatically by Ninject.Extensions.Factory with custom instance providers and custom binding generators, but I can't get past this problem.
Would having more control over the factory implementation (in other words, use my own Factory, not the automatic factory from Ninject.Extensions.Factory) help? In the past, I've used Assembly reflection to find candidate types, and use Activation.CreateInstance() to create either the specific implementation I need or a default implementation, but this gets really cumbersome once those implementations have their own constructor-injected dependencies as Dependency-injection principles are applied to those implementations. Hence, my shift to IoC containers for a solution - but this is not working out like I would have hoped.
UPDATE 1 -- SUCCESS USING MY OWN FACTORY IMPLEMENTATION
I'm not really happy with this, because every time a new implementation of IWidget
must be written, I'll have to crack open this factory and update it, too. In my example here, I'd have to add another line in the bindings, too - but that's where convention-based binding would come in, which I would plan on using to avoid having to constantly update the binding definitions.
Using this factory implementation,
public interface IWidgetFactory { IWidget Create(string name); }
public class WidgetFactory : IWidgetFactory
{
private readonly IKernel kernel;
public WidgetFactory(IKernel kernel) { this.kernel = kernel; }
public IWidget Create(string name)
{
switch (name)
{
case "Blue":
return this.kernel.Get<IWidget>(typeof (BlueWidget).Name);
default:
return this.kernel.Get<IWidget>(typeof (DefaultWidget).Name);
}
}
}
I can get this test to pass:
[Fact]
public void WidgetBuilders_And_Customizers_And_Bindings_Oh_My()
{
StandardKernel kernel = new StandardKernel();
kernel.Bind<IWidget>().To<DefaultWidget>().Named(typeof(DefaultWidget).Name);
kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof (BlueWidget).Name);
kernel.Bind<IWidgetFactory>().To<WidgetFactory>().InSingletonScope();
kernel.Get<IWidgetFactory>().Create("Blue")
.Should().BeOfType<BlueWidget>();
kernel.Get<IWidgetFactory>().Create("Red")
.Should().BeOfType<DefaultWidget>();
}
It works, but it doesn't feel right, for the following reasons:
- I have to inject the
IKernel
into theIWidgetFactory
- For every new implementation of
IWidget
, theIWidgetFactory
must be updated - It seems like a Ninject extension should have been created for this scenario already
END OF UPDATE 1
What would you do in this situation, considering that the count of IWidget
implementations is high, the anticipated range of "widget name" arguments is essentially infinite, and all unresolvable widget names should be handled with the DefaultWidget
?
You need not read further, but if you're interested, here are the various tests I tried as I worked on this problem:
Here's the complete evolution of tests I went through:
[Fact]
public void Unknown_Type_Names_Resolve_To_A_Default_Type()
{
StandardKernel kernel = new StandardKernel();
// evolution #1: simple as possible
// PASSES (as you would expect)
//kernel.Bind<IWidget>().To<BlueWidget>();
//kernel.Get<IWidget>().Should().BeOfType<BlueWidget>();
// evolution #2: make the only binding to a different IWidget
// FAILS (as you would expect)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Get<IWidget>().Should().BeOfType<BlueWidget>();
// evolution #3: add the binding to `BlueWidget` back
// ACTIVATION EXCEPTION (more than one binding for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>();
//kernel.Get<IWidget>().Should().BeOfType<BlueWidget>();
// evolution #4: make `BlueWidget` binding a *named binding*
// ACTIVATION EXCEPTION (more than one binding for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof (BlueWidget).Name);
//kernel.Get<IWidget>().Should().BeOfType<BlueWidget>();
// evolution #5: change `Get<>` request to specifiy widget name
// PASSES (yee-haw!)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof(BlueWidget).Name);
//kernel.Get<IWidget>("BlueWidget").Should().BeOfType<BlueWidget>();
// evolution #6: make `BlueWidget` binding *non-named*
// ACTIVATION EXCEPTION (**NO matching bindings available** for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>();
//kernel.Get<IWidget>("BlueWidget").Should().BeOfType<BlueWidget>();
// evolution #7: ask for non-existance `RedWidget`, hope for `DefaultWidget`
// ACTIVATION EXCEPTION (**NO matching bindings available** for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>();
//kernel.Get<IWidget>("RedWidget").Should().BeOfType<DefaultWidget>();
// evolution #8: make `BlueWidget` binding a *named binding* again
// ACTIVATION EXCEPTION (**NO matching bindings available** for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof(BlueWidget).Name);
//kernel.Get<IWidget>("RedWidget").Should().BeOfType<DefaultWidget>();
// evolution #9: remove `RedWidget` specification in Get<> request
// ACTIVATION EXCEPTION (back to **more than one** binding for `IWidget`)
//kernel.Bind<IWidget>().To<DefaultWidget>();
//kernel.Bind<IWidget>().To<BlueWidget>().Named(typeof(BlueWidget).Name);
//kernel.Get<IWidget>().Should().BeOfType<DefaultWidget>();
}
.Binding.IsImplicit
which doesn't seem to exist anymore. I've been looking on SO for anything including "Ninject Default Fallback Binding", etc, but not much has turned up of use. I've asked a more abstract question on programmers stackexchange to see if this is just a bad idea to start with. – Linguistician