Ondrej Svejdar
Ondrej Svejdar

Reputation: 22054

Constructor dependency injection via unity with parameters from HttpContext

We're using domain to customize how our application behaves. I'll illustrate it on example:

// default behavior
public class CoreService : IService {
  public virtual string Hello { get { return "Hello"; } }
  public virtual string FavouriteDrink { get { return "Water"; } }
}

// german.site.com
public class GermanService : CoreService {
  public override string Hello { get { return "Gutten tag"; } }
  public override string FavouriteDrink { get { return "Beer"; } }
}

// usa.site.com
public class UsaService : CoreService {
  public override string FavouriteDrink { get { return "Cofee"; } }
}

Services are bootstrapped as follow:

var container = new UnityContainer();
container.RegisterType<IService, CoreService>();
container.RegisterType<IService, GermanService>("german.site.com");
container.RegisterType<IService, UsaService>("usa.site.com");

I use Unity to bootstrap mvc controllers. IE:

public class HomeController : Controller {
  private IService m_Service;

  // contructor dependency injection magic - this resolves into "CoreService"
  public HomeController([Dependency]IService service) {
    if (service == null) {
      throw new ArgumentNullException("service");
    }
    m_Service = service;
  } 
}

Is there a way how to change unity resolution so it'll take domain into account ? Right now I ended up with

public class HomeController : Controller {
  private IService m_Service;

  // contructor dependency injection magic - a lot less magical
  public HomeController() {
    m_Service = DomainServiceLocator.Retrieve<IService>();
  } 
}

Support classes:

public static class DomainServiceLocator {
  private static UnityContainerAdapter adapter; 

  public static T Retrieve<T>() {
    string domain = HttpContext.Current.Request.Url.Host;
    if (adapter.IsServiceRegistered(typeof(T), domain)) {
      return adapter.Resolve<T>(domain);
    }

    return adapter.Resolve<T>();
  }
}

public class QueryableContainerExtension : UnityContainerExtension {
  private List<RegisterInstanceEventArgs> registeredInstances = new List<RegisterInstanceEventArgs>();
  private List<RegisterEventArgs> registeredTypes = new List<RegisterEventArgs>();

  protected override void Initialize() {
    this.Context.Registering += (sender, e) => { this.registeredTypes.Add(e); };
    this.Context.RegisteringInstance += (sender, e) => { this.registeredInstances.Add(e); };
  }


  public bool IsServiceRegistered(Type service, string name) {
    return registeredTypes.FirstOrDefault(e => e.TypeFrom == service && e.Name == name) != null
           || registeredInstances.FirstOrDefault(e => e.RegisteredType == service && e.Name == name) != null;
  }
}

public class UnityContainerAdapter {
  private readonly QueryableContainerExtension queryableContainerExtension;
  private readonly IUnityContainer unityContainer;

  public UnityContainerAdapter()
    : this(new UnityContainer()) {
  }

  public UnityContainerAdapter(IUnityContainer unityContainer) {
    this.unityContainer = unityContainer;

    // adding extensions to unity container
    this.queryableContainerExtension = new QueryableContainerExtension();
    unityContainer.AddExtension(this.queryableContainerExtension);
  }

  public T Resolve<T>(string name) {
    return unityContainer.Resolve<T>(name);
  }

  public T Resolve<T>() {
    return unityContainer.Resolve<T>();
  }

  public bool IsServiceRegistered(Type service, string name) {
    return this.queryableContainerExtension.IsServiceRegistered(service, name);
  }
}

Upvotes: 3

Views: 1350

Answers (2)

Ondrej Svejdar
Ondrej Svejdar

Reputation: 22054

Below is the solution I ended up with - it is based on @Spencer idea. I've created a factory, default implementation to the factory has a reference to DI container itself (IUnityContainer in my case), so it can perform the resolution based on domain once it is asked to. It is also more "modern friendly" since in current generation of ASP.NET (ASP.NET CORE) there is no such thing as magic singleton providing current HttpContext and DI is hard coded into the framework.

public interface IFactory<T>
{
    T Retrieve(string domain);
}   

internal sealed class Factory<T> : IFactory<T>
{
    private readonly IUnityContainer _container;

    public Factory(IUnityContainer container)
    {
        _container = container;
    }

    public T Resolve(string domain)
    {
        // this is actually more complex - we have chain inheritance here
        // for simplicity assume service is either registered for given 
        // domain or it throws an error
        return _container.Resolve<T>(domain);
    }
}

// bootstrapper
var container = new UnityContainer();
container.RegisterType<IService, CoreService>();
container.RegisterType<IService, GermanService>("german.site.com");
container.RegisterType<IService, UsaService>("usa.site.com");
container.RegisterInstance<IFactory<IService>>(new Factory<IService>(container));    

And the home controller looks like

public class HomeController : Controller {
  private IFactory<IService> m_Factory;

  public HomeController(IFactory<IService> factory) {
    m_Factory = factory;
  } 

  private void FooBar() {
    var service = m_Factory.Retrieve(this.HttpContext.Uri.Host);
    var hello = service.Hello;
  }   
}

Its also a worth mentioning that - as I'm lazy - I've build a system of decorative attributes like

[Domain("german.site.com")]
public class GermanService : IService { ... }

[DomainRoot]
public class CoreService : IService { ... }

[Domain("usa.site.com")]
public class UsaService : CoreService { ... }

So the bootstrapping is done automatically across all types in given assembly. But that part is a bit lengthy - if anyone is interested I can post it on github.

Upvotes: 0

Spencer
Spencer

Reputation: 271

I like to use an injection factory in these scenarios when resolving something at runtime. Essentially you're resolving your type via the domain name:

So in your composition root you could register like this:

container.RegisterType<Func<string, IService>>
                (
                    new InjectionFactory(c => new Func<string, IService>(name => c.Resolve<IService>(name)))
                );

Then in your HomeController you can inject the delegate

public class HomeController
{
    private readonly Func<string,IService> _serviceFactory;

    public HomeController(Func<string, IService> serviceFactory)
    {
        if(serviceFactory==null)
            throw new ArgumentNullException("serviceFactory");

        this._serviceFactory= serviceFactory;
    }

    public void DoSomethingWithTheService()
    {
        var domain = this.HttpContext.Uri.Host;                             
        var service = this._serviceFactory(domain);
        var greeting = service.Hello;
    }
}

```

This is then still unit testable and you have not leaked the DI contain implementation outside of "composition root".

Also.. should CoreService be abstract to avoid direct instantiation of it?

Upvotes: 1

Related Questions