0

I have a following problem. I register my components and initialize them in Unity like this (example is for a Console application):

public class SharePointBootstrapper : UnityBootstrapper
{
    ...

    public object Initialize(Type type, object parameter) =>
        Container.Resolve(type,
            new DependencyOverride<IClientContext>(Container.Resolve<IClientContext>(parameter.ToString())),
            new DependencyOverride<ITenantRepository>(Container.Resolve<ITenantRepository>(parameter.ToString())));

    public void RegisterComponents()
    {
        Container
            .RegisterType<IClientContext, SharePointOnlineClientContext>(SharePointClientContext.Online.ToString())
            .RegisterType<IClientContext, SharePointOnPremiseClientContext>(SharePointClientContext.OnPremise.ToString())
            .RegisterType<ITenantRepository, DocumentDbTenantRepository>(SharePointClientContext.Online.ToString())
            .RegisterType<ITenantRepository, JsonTenantRepository>(SharePointClientContext.OnPremise.ToString());
    }
}

public enum SharePointClientContext
{
    Online,
    OnPremise
}

class Program
{
    static void Main(string[] args)
    {
        ...

        bootstrap.RegisterComponents();
        var bla = bootstrap.Initialize(typeof(ISharePointManager), SharePointClientContext.Online);
    }
}

So, I register my components in MVC, WCF, Console etc. once with RegisterComponents() and initialize them with Initialize().

My question is, if I want to initialize specific named registration at runtime, from e.g. user input, can it be done otherwise as the code presented (with InjectionFactory or similar)?

This code works fine, but I'm not happy with its implementation. I have a feeling that it could be written in RegisterComponents() instead of Initialize() so that it accepts a parameter of some type, but I don't know how to do it.

Or, is maybe my whole concept wrong? If so, what would you suggest? I need to resolve named registration from a parameter that is only known at runtime, regardless of the technology (MVC, WCF, Console, ...).

Thanks!

civan
  • 254
  • 3
  • 9

1 Answers1

0

Instead of doing different registrations, I would do different resolves.

Let's say that you need to inject IClientContext, but you want different implementations depending on a runtime parameter.

I wrote a similiar answer here. Instead of injecting IClientContext, you could inject IClientContextFactory, which would be responsible for returning the correct IClientContext. It's called Strategy Pattern.

public interface IClientContextFactory
{
    string Context { get; } // Add context to the interface.
}

public class SharePointOnlineClientContext : IClientContextFactory
{
    public string Context 
    {
        get 
        {
            return SharePointClientContext.Online.ToString(); 
        }
    }
}

// Factory for resolving IClientContext.
public class ClientContextFactory : IClientContextFactory
{
    public IEnumerable<IClientContext> _clientContexts;

    public Factory(IClientContext[] clientContexts)
    {
        _clientContexts = clientContexts;
    }

    public IClientContext GetClientContext(string parameter)
    {
        IClientContext clientContext = _clientContexts.FirstOrDefault(x => x.Context == parameter);
        return clientContext;
    }
}

Register them all, just as you did. But instead of injecting IClientContext you inject IClientContextFactor.

There also another solution where you use a Func-factory. Look at option 3, in this answer. One may argue that this is a wrapper for the service locator-pattern, but I'll leave that discussion for another time.

public class ClientContextFactory : IClientContextFactory
{
    private readonly Func<string, IClientContext> _createFunc;

    public Factory(Func<string, IClientContext> createFunc)
    {
        _createFunc = createFunc;
    }

    public IClientContext CreateClientContext(string writesTo)
    {
        return _createFunc(writesTo);
    }
}

And use named registrations:

container.RegisterType<IClientContext, SharePointOnlineClientContext>(SharePointClientContext.Online.ToString());
container.RegisterType<IClientContext, SharePointOnPremiseClientContext>(SharePointClientContext.OnPremise.ToString());

container.RegisterType<IFactory, Factory>(
    new ContainerControlledLifetimeManager(), // Or any other lifetimemanager.
    new InjectionConstructor(
        new Func<string, IClientContext>(
            context => container.Resolve<IClientContext>(context));

Usage:

public class MyService
{
    public MyService(IClientContextFactory clientContextFactory)
    {
        _clientContextFactory = clientContextFactory;
    }

    public void DoStuff();
    {
        var myContext = SharePointClientContext.Online.ToString();
        IClientContextclientContext = _clientContextFactory.CreateClientContext(myContext);
    }
}
Community
  • 1
  • 1
smoksnes
  • 10,509
  • 4
  • 49
  • 74
  • Yes, I've read about Mark Seaman's approach with factories, but you have nicely clarified it. Just to revise, you recommend that I move the runtime parameter from intialization in the Container to the method inside a factory that I register instead of my IClientContext's (e.g. RegisterType())? I find that acceptable, but in which case would I use named registrations? – civan Aug 09 '16 at 09:16
  • I updated my answer. I try to avoid named registrations. But in order to inject them as an array into the factory I believe that you need to use named registrations. But the registrations aren't based on a runtime parameter. You just resolve them based on a runtime parameter. – smoksnes Aug 09 '16 at 09:58