Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Ninject and binding a default implementation while avoiding the dreaded Service Locator anti-pattern

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:

  1. I have to inject the IKernel into the IWidgetFactory
  2. For every new implementation of IWidget, the IWidgetFactory must be updated
  3. 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>();
}
like image 501
Jeff Avatar asked Apr 16 '13 21:04

Jeff


People also ask

How to implement Dependency Injection in c# using Ninject?

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.

What is a ninject module?

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.


1 Answers

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>();
}
like image 78
CodeChops Avatar answered Sep 18 '22 13:09

CodeChops