We have a multi-tennant ASP.NET MVC application that hosts a booking engine for multiple clients. Each of these clients has multiple packages that can influence Unity Container configuration. We are creating a child container per request and registering different interface implementations based on the client and package parameters passed through the route.
Currently we are accomplishing this by doing the following:
This works but is really clumsy and I feel eventually it will be unsustainable. I'm looking for a better solution.
We're stuck on .NET 4.0 at the moment until we wrap up some legacy stuff so I'm targeting Unity 2 specifically.
I've tried creating a custom IDependencyResolver to create the child container and register dependencies based on route parameters storing the container in either Session or in HttpContext items but ran into the null HttpContext problems. Is there any other way to base registrations on the route and have the dependencies injected to the controller constructor?
Eventually I will need a solution for Web API as well.
Edit: Example
public interface IRateService { ... }
public class RemoteRateService : IRateService { ... }
public class LocalRateService : IRateService { ... }
public class CustomDependencyResolver : IDependencyResolver
{
public object GetService(Type serviceType)
{
if(ChildContainer == null)
{
ChildContainer = _container.CreateChildContainer();
var routeData = HttpContext.Current.Request.RequestContext.RouteData.Values;
if(routeData["client"] == "ClientA")
ChildContainer.RegisterType<IRateService, RemoteRateService>();
else
ChildContainer.RegisterType<IRateService, LocalRateService>();
}
return ChildContainer.Resolve(serviceType);
}
}
public class RateController : Controller
{
private IRateService _rateService;
public RateController(IRateService rateService)
{
_rateService = rateService;
}
...
}
url: /ClientA/Package1/Rate - RateController gets RemoteRateService
url: /ClientB/Package2/Rate - RateController gets LocalRateService
Abatishchev answered my question in the comments by pointing me in the right direction with IControllerFactory. For the random google searches that end here, here is the basic setup I used by inheriting from DefaultControllerFactory:
public class UnitySessionControllerFactory : DefaultControllerFactory
{
private const string HttpContextKey = "Container";
private readonly IUnityContainer _container;
public UnitySessionControllerFactory (IUnityContainer container)
{
_container = container;
}
protected IUnityContainer GetChildContainer(RequestContext requestContext)
{
var routeData = requestContext.RouteData.Values
?? new RouteValueDictionary();
var clientName = routeData["clientName"] as string;
var packageId = routeData["packageID"] as int?;
if (clientName == null)
throw new ArgumentException("ClientName not included in route parameters");
var childContainer = requestContext.HttpContext.Session[clientName + HttpContextKey] as IUnityContainer;
if (childContainer != null)
return childContainer;
requestContext.HttpContext.Session[clientName + HttpContextKey] = childContainer = _container.CreateChildContainer();
var moduleLoader = childContainer.Resolve<ModuleLoader>();
moduleLoader.LoadModules(clientName, packageId);
return childContainer;
}
public override IController CreateController(RequestContext requestContext, string controllerName)
{
var controllerType = GetControllerType(requestContext, controllerName);
var container = GetChildContainer(requestContext);
return container.Resolve(controllerType) as IController;
}
public override void ReleaseController(IController controller)
{
_container.Teardown(controller);
}
}
Forgive the use of session here. In the future I will exchange it for HttpContext.Items once I am able to wrangle in our project's use of session.
To enable the custom controller factory I added this line to the Bootstrapper.Initialise() method
ControllerBuilder.Current
.SetControllerFactory(new UnitySessionControllerFactory(container));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With