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:

  1. When using true IoC, I need the IXXXServices injected, so I get an area specific class instead of the Autofac IComponentContext
  2. Each area is associated with a different database, but a shared schema - therefore using the same EF library, only with a different connection string.

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):

  • Is there a way to dynamically select a set of mapped Autofac registrations by the area, which will automatically resolve to new controllers?
Was it helpful?

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);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top