Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dynamically register a type (eg. a different implementation) for a Unity container based on web application URL?

Currently I am stuck on a what I assume to be a simple architecture problem.

I have a controller (these are just examples as I can't share my real code, but the principle holds):

public class StackOverflowController : Controller
    {
        private readonly IStackOverflowService stackOverflowService;

        public StackOverflowService (IStackOverflowService stackOverflowService)
        {
            this.stackOverflowService = stackOverflowService;
        }

        // GET: StackOverflow
        public ActionResult Index()
        {

            var foo = stackOverflowService.Get(bar);

            return View(foo);
        }
    }

I am using the Unity MVC4 Bootstrapper to manage the injections:

  public static class Bootstrapper
    {

        public static IUnityContainer Initialise()
        {
            var container = BuildUnityContainer();

            DependencyResolver.SetResolver(new UnityDependencyResolver(container));

            return container;
        }

        private static IUnityContainer BuildUnityContainer()
        {
            var container = new UnityContainer();

            container.RegisterType<IStackOverflowService, StackOverflowService>("DefaultStackOverflowService");


            return container;
        }


    }

So far so good, the injections work. Where I get stuck is to now easily based on either configuration or some variable I want to be able to change the implementation of the StackOverflowService.

So for instance if the url = "somethingelse.com" I want to be able to automatically inject the CustomStackOverflowService which inherits from IStackOverflowService. But this is where I get lost. I tried using Named Registration but I can't seem to work out how I would manually resolve the correct service to implement based on some alternate criteria.

If anyone could please help that would be epic :)

EDIT: The criteria for the change of implementation would ideally be figured out in realtime, so say based on the actual url that is currently being visited. It's a multi-tennanted environment so it could be something like domain.com/stackoverflow vs domain2.com/stackoverflow. The implementation needs to be different per site. I don't even know if this is possible to be honest or how to go about that. I am fairly new to the whole IoC subject.

EDIT 2: I have gotten a little bit further with this and managed to manually invoke a particular implementation. I am now looking at using a Custom Controller Factory to resolve what implementation to use, but I am not sure if this is the right way to go, or if there is an easier way of doing this.

I need to keep in mind that as more clients come on board I need to be able to quickly add more implementations for each new clients. So ideally I would have some easy way to manage the methods for a particular client.

EDIT 3: Further update, I have rewritten my controller to allow for a constructor to be injected to determine the dependency. However my initial issue is still valid, as this fires when the controller is constructed, I don't have access to the request, and can therefore not determine the URL to resolve the dependency. Any ideas would be appreciated.

like image 407
JanR Avatar asked Oct 15 '14 01:10

JanR


1 Answers

Using the IControllerFactory and child containers should work but will tightly couple your MVC dependency resolver to Unity.

First change your bootstrapper to create a child container per domain and configure each child container with the special registrations you need for that domain. Plus we need to tell MVC that we are have a IControllerFactory we want to use

public class Bootstrapper
{
    public static IUnityContainer Initialize()
    {
        var container = BuildUnityContainer();

        DependencyResolver.SetResolver(new UnityDependencyResolver(container));

        return container;
    }

    private static IUnityContainer BuildUnityContainer()
    {
        var container = new UnityContainer();

        // Register unity controller factory to resolve controllers
        container.RegisterType<IControllerFactory, UnityControllerFactory>();

        // default registrations
        container.RegisterType<IStackOverFlowService, StackOverflowService>();

        // Do special registration per domain
        LocalHostRegistrations(container);
        SomeThingElseRegistrations(container);

        return container;
    }

    private static void LocalHostRegistrations(IUnityContainer container)
    {
        // you can create a child container per domain
        var localHostContainer = container.CreateChildContainer();
        // all special registrations for localhost go in this container
        // any registrations that are the same as the default we can leave off and unity will 
        //  auto find them from the parent
        localHostContainer.RegisterType<IStackOverFlowService, LocalHostStackOverflowService>();

        // register the child container in the parent container with a registration name
        container.RegisterInstance(typeof(IUnityContainer), "localhost", localHostContainer);
    }

    private static void SomeThingElseRegistrations(IUnityContainer container)
    {
        var localHostContainer = container.CreateChildContainer();
        localHostContainer.RegisterType<IStackOverFlowService, CustomStackOverflowService>();
        container.RegisterInstance(typeof(IUnityContainer), "somethingelse", localHostContainer);
    }
}

Now we create the controller factory

public class UnityControllerFactory : DefaultControllerFactory 
{
    public override IController CreateController(RequestContext requestContext, string controllerName)
    {
        IController controller = null;
        var type = GetControllerType(requestContext, controllerName);
        if (type != null)
        {
            // read host from incoming url
            var domain = requestContext.HttpContext.Request.Url.Host.ToLower();
            // get parent unity container
            var container = DependencyResolver.Current.GetService<IUnityContainer>();
            // check if there is a child container for this domain
            if (container.IsRegistered<IUnityContainer>(domain))
            {
                container = container.Resolve<IUnityContainer>(domain);
            }

            controller = container.Resolve(type) as IController;
        }

        // if didn't find type or not right type just pass into base to handle errors
        if (controller == null)
        {
           return base.CreateController(requestContext, controllerName);
        }
        return controller;
    }
}

This factory will retrieve the parent unity container from the MVC dependency resolver and see if any child containers are registered. If so it will use the child container to resolve the controller and inject any registrations that are special to the childs container. This does tightly couple the unity container to dependency resolver of MVC and is by-passing the dependency resolver and using unity directly, but should give you what you are looking for.

like image 73
CharlesNRice Avatar answered Sep 29 '22 08:09

CharlesNRice