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:
IKernel
into the IWidgetFactory
IWidget
, the IWidgetFactory
must be updatedEND 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>();
}
Step 1: We are creating an instance of Class StandardKernel. Step 2: Then we will load the Kernel. Step 3: Get the instance of the specific service that we want to inject. Step 4: Then inject the dependency.
The Ninject modules are the tools used to register the various types with the IoC container. The advantage is that these modules are then kept in their own classes. This allows you to put different tiers/services in their own modules.
Don't know where this stacks up, but it works. You can not pass an named parameter in though, it only works with an empty parameter. In other words you can't do this:
var stuff1 = kernel.Get<IWidget>("OrangeWidget");
Unless OrangeWidget exists. Rather to get the default you need to do this:
var stuff2 = kernel.Get<IWidget>();
Here is an example:
IDictionary dic = new Dictionary<string, string>();
dic.Add("BlueWidget", "BlueWidget");
dic.Add("RedWidget", "RedWidget");
kernel.Bind<IWidget>().To<DefaultWidget>()
.When(x => x.Service.Name != (string)dic[x.Service.Name]);
kernel.Bind<IWidget>().To<BlueWidget>().Named("BlueWidget");
var stuff1 = kernel.Get<IWidget>("BlueWidget");
var stuff2 = kernel.Get<IWidget>();
This is a cool Ninject post you might be interested in...
I'd like to add something about Named parameters. This is based on this documentation. The Named parameter allows you to call out your bindings by name when you Get<> them, but it does not "default" to anything. So you would actually have to pass the name DefaultWidget to get that binding. This works:
kernel.Bind<IWidget>().To<BlueWidget>().Named("BlueWidget");
kernel.Bind<IWidget>().To<DefaultWidget>().Named("DefaultWidget");
var blueOne = kernel.Get<IWidget>("BlueWidget");
var defaultOne = kernel.Get<IWidget>("DefaultWidget");
If anyone can figure out how to implement a default, I'd be curious to know how, though I've never needed it. I'm a student of Ninject and really like it.
UPDATE:
I got it. Found a cool solution here.
I created a class to extend Ninject:
public static class NinjectExtensions
{
public static T GetDefault<T>(this IKernel kernel)
{
return kernel.Get<T>(m => m.Name == null);
}
public static T GetNamedOrDefault<T>(this IKernel kernel, string name)
{
T result = kernel.TryGet<T>(name);
if (result != null)
return result;
return kernel.GetDefault<T>();
}
}
Here is your Unknown_Type method...
public static void Unknown_Type_Names_Resolve_To_A_Default_Type()
{
StandardKernel kernel = new StandardKernel();
IDictionary dic = new Dictionary<string, string>();
dic.Add("BlueWidget", "BlueWidget");
dic.Add("RedWidget", "RedWidget");
kernel.Bind<IWidget>().To<DefaultWidget>().When(x => x.Service.Name != (string)dic[x.Service.Name]);
kernel.Bind<IWidget>().To<BlueWidget>().Named("BlueWidget");
// this works!
var sup = kernel.GetNamedOrDefault<IWidget>("Not here");
var stuff1 = kernel.Get<IWidget>("BlueWidget");
var stuff2 = kernel.Get<IWidget>();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With