I wanted to recommend a custom LifetimeScope
per area, but it seems that there is an existing solution.
Take a look at this: https://code.google.com/p/autofac/wiki/MultitenantIntegration.
Question
I've got an MVC application split into a few areas:
+ Root
+ -- Areas
--+ US
+ -- USCodeController : XXXCodeBaseController
--+ UK
-- UKCodeController : XXXCodeBaseController
--+ (Other countries pending)
+ -- Controllers
--+ XXXCodeBaseController
So I've defined some base functionality in the Controllers
folder and then inherited and extended the functionality in the areas folder. This way UI's can be customised around UI components specific for each country, workflow etc.
When it comes to using Autofac, what I am having to do is pass the IComponentContext
up to the constructor parameter, which isn't very good. The problems include:
IComponentContext
The design should definitely be more elegant by injection services directly - which is what I am after. But the resolver doesn't seem to be flexible enough to resolve the controllers without a massive hack that is becoming quite a large wireup for every controller used. Here is an example of if you wire up a controller manually:
builder.Register(s => new OrderService(r.ResolveNamed<IOrderRepository>("US"))
.Named("US")
.InstancePerHttpRequest();
// Lets say we add a DeclinedOrderController
public class DeclinedOrderControllerBase
{
public DeclinedOrderControllerBase ( IDeclinedOrderService service )
{ }
}
To get the hook up working, you need the registration added to the Autofac, as we need a US/UK specific connection string in the DeclinedOrderService
builder.Register(s => new DeclinedOrderController(r.ResolvedName<IDeclinedOrderService>("US")
.Named("US")
.InstancePerHttpRequest();
My question is (finally):
Solution
I wanted to recommend a custom LifetimeScope
per area, but it seems that there is an existing solution.
Take a look at this: https://code.google.com/p/autofac/wiki/MultitenantIntegration.
OTHER TIPS
In MVC4, you can create a custom controller factory to capture controller instances when they are created. You can use this as a single composition point instead of having to do it in each controller.
Create a custom controller factory by inheriting from System.Web.Mvc.DefaultControllerFactory
. Override the GetControllerInstance
method and let the base function create the controller for you. Now you can have your dependency injection engine compose the controller.
public class AreaControllerFactory : DefaultControllerFactory
{
// We use a controller factory to hook into the MVC pipeline in order to get a reference to controllers
// as they are being created. We inherit from the default controller factory because we do not want to
// have to locate the appropriate controllers ourself (we only want a reference to it). Once we have a
// reference, we simply hand the controller off for composition.
protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
{
IController controller = base.GetControllerInstance(requestContext, controllerType); // create the controller
if (controller != null)
{
var container = ...; //construct your composition container
container.ComposeParts(controller); //compose your controller
}
return controller;
}
}
Register your controller factory in the Application_Start
method in Global.asax.cs
.
var controllerFactory = new AreaControllerFactory();
ControllerBuilder.Current.SetControllerFactory(controllerFactory);
To filter composition objects by area, I build a custom, global area components dictionary (e.g. a dictionary of area name, list of applicable types) for each area.
// NOTE:ComposablePartCatalog is MEF specific, change as needed
internal static class AreaComponents
{
/// <summary>
/// A list of Area name, catalog pairs.
/// Each Area can provide a custom list of components to import from.
/// </summary>
internal static readonly Dictionary<string, ComposablePartCatalog> AreaCompositionCatalogs =
new Dictionary<string, ComposablePartCatalog>();
}
Populate the dictionary inside of the RegisterArea
method of each area's AreaRegistration
implementation.
public class XAreaRegistration : AreaRegistration
{
public override void RegisterArea(AreaRegistrationContext context)
{
/* ... Standard route logic ... */
// Set up an MEF catalog with Area components
var xCatalog = new AssemblyCatalog(
typeof(MyPluginsNamespace.ArbitraryTypeToLookupAssembly).Assembly);
AreaComponents.AreaCompositionCatalogs["X"] = xCatalog;
}
}
Use this dictionary to select the appropriate subset of composition objects in my custom controller factory when constructing the composition container for the given controller.
// Capture current area name
System.Web.HttpContextBase contextBase = new System.Web.HttpContextWrapper(System.Web.HttpContext.Current);
System.Web.Routing.RouteData routeData = System.Web.Routing.RouteTable.Routes.GetRouteData(contextBase);
object areaObject = routeData.DataTokens["area"];
string areaName = areaObject as string ?? string.Empty;
// Create a composition container specific to this area
ComposablePartCatalog areaCatalog =
AreaMefComponents.AreaCompositionCatalogs.ContainsKey(areaName) ?
AreaMefComponents.AreaCompositionCatalogs[areaName] : null;
var container = new CompositionContainer(areaCatalog);