2

I was looking at another question:

Exception is: InvalidOperationException - The current type, is an interface and cannot be constructed. Are you missing a type mapping?

Everyone scolded the person asking the question for 'doing it wrong'. But if you look at all the examples and sites describing this, they all describe injecting an interface into a Controller, typically via the constructor.

The problem here is that suppose I have a Web API which, for example returns a phrase in a different language:

http://mywebapi/api/SayHello/FR

The FR tells the WebAPI that we want Hello in French. I could easily use English, Chinese or any other language.

Now, I decide to build a set of Assemblies, one for each language, all implementing an interface called ILanguage. I make a Unity Container, put named type mappings in the config file (resolving the ILanguage interface with "FR" would return a ILanguage implemented by the French assembly, etc).

The code does NOT know when it's called WHICH implementation it's going to get. Injecting an ILanguage implementation into the Controller constructor seems wrong. Only when the URL is parsed and we get into the method do we see the "FR" parameter passed in, and that tells us to call:

container.Resolve<ILanguage>("FR")  

to get the correct ILanguage interface for calling to return the appropriate phrase.

A dogmatic "never call container.Resolve" in your code anywhere, sounds very nice and purist, but it doesn't solve this problem. So, what is the recommended approach? It looks a lot like a ServiceLocator in the sense that we want to find a service dynamically using a 'key' of some kind, but I certainly do NOT want my Web API controller assembly having direct knowledge of all these little language assemblies. I have this working using the system above, but I'm wondering what all the DI/IoC purists would say about this code, and if they don't like it, how they solve the 'dynamic plug-in' problem in a Web API Controller.

Community
  • 1
  • 1
Randy Magruder
  • 171
  • 3
  • 11

1 Answers1

1

I would recommend making your controllers accept a language factory (i.e. a Func<string, ILanguage>) that returns the ILanguage implementation based on the language code you pass into it.

The reason that a factory function should be favored over not declaring any dependencies in your constructor and instead calling container.Resolve() is that the latter obscures the fact that you depend on ILanguage, whereas taking a dependency on a Func<string, ILanguage> makes this very clear.

I.e.:

public interface ILanguage
{
    string SayHello();
}

public class Program
{
    public static void Main(string[] args)
    {
        UnityContainer container = new UnityContainer();
        container.RegisterType<Func<string, ILanguage>>(new InjectionFactory(con => LanguageFactory));

        //Use it:
        MyController controller = container.Resolve<MyController>();
        string result = controller.TalkToMe("en");
    }

    private static Func<string, ILanguage> LanguageFactory = delegate(string languageCode)
    {
        //Create the correct ILanguage here based on the languageCode. 
        //It's OK to call container.Resolve() here, for example to 
        //resolve named instances.
        return (ILanguage)null;
    };
}

public class MyController
{
    private Func<string, ILanguage> _languageFactory;

    public MyController(Func<string, ILanguage> languageFactory)
    {
        _languageFactory = languageFactory;
    }

    public string TalkToMe(string languageCode)
    {
        ILanguage language = _languageFactory(languageCode);
        return language.SayHello();
    }
}

As an alternative, you could also use the IUnityContainer that you get passed into the InjectionFactory to do the resolving in the language factory:

container.RegisterType<Func<string, ILanguage>>(new InjectionFactory(con => CreateLanguageFactory(con)));

//...

private static Func<string, ILanguage> CreateLanguageFactory(IUnityContainer container)
{
    return delegate(string languageCode)
    {
        //Create the correct ILanguage here based on the languageCode.
        ILanguage result = container.Resolve<ILanguage>(languageCode);
        return result;
    };
}
Leon Bouquiet
  • 4,159
  • 3
  • 25
  • 36