Friday, July 9, 2010

MEF and MVC - Limitations and workarounds for partial trust environments

A while back I wrote about using MEF in MVC environments with extensions provided by Hammet for the Nerd Dinner MEF sample application. Those extensions deal with dynamic discovery of parts based on MVC conventions (instead of attributes), as well as per-request composition containers. The extensions work great, after a few modifications that I talked about in the last post... but in partial trust environments it blows up in your face!

BOOM!

I spent hours and hours digging through the code, reading about CAS, trust policies, transparent code and whole mess of other junk that I'd really rather not have rattling around my skull. Long story short -- MEF isn't friendly with partially trusted asp.net environments.

Now, you could write your custom MEF code in a class library, flag the assembly with the APTCA attribute, sign-it, and install it to the GAC if you want. That will get around these limitations neatly, but if you are running in partial trust you probably don't have the luxury of installing things to the GAC either.

The first major limitation is that you cannot access the parts collection within catalogs or containers. If you try it, your get an exception like this:

Attempt by method 'DynamicClass.lambda_method(System.Runtime.CompilerServices.Closure)' 
to access type 'System.ComponentModel.Composition.Hosting.ComposablePartCatalogCollection' failed.
  
  
The easiest way to reproduce the problem is to simply add the trust element to web.config like this:

<trust level="High"/>
  
  
Then add this to application startup in global.asax:

 var cat = new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin"));
 var parts = cat.Parts.ToArray();
  
   
In the Nerd Dinner MEF sample, this limitation effectively kills the mechanisms that slit up parts into per-request containers vs. application wide containers.

If you've done any reading about MEF online, you've likely run across code for a FilteredCatalog class. This thing is so commonly cited on the net that it seems beyond retarded that it wasn't built-in with MEF. But these limitations from partial trust kills FilteredCatalog; which the Nerd Dinner MEF sample uses heavily.

The other major area of limitation is that you cannot use ReflectionModelServices, which is needed in order to dynamically create export/import definitions programmatically. This kills the Nerd Dinner MEF sample's auto-discovery of controllers.

Despite these limitations, you can still use MEF in medium trust, but only if you are careful to keep it simple and straight forward.

Honestly, I recommend that you just use Ninject or a similar IoC/DI framework until the next version of MEF or MVC (hopefully) fixes these issues.

In my case though, I really wanted to be able to support medium trust environments and I'm too damned stubborn to give up on MEF that easy.

I'm OK with having to use the MEF attributes to decorate my controllers, so losing auto-discovery isn't much of a problem. Hammet's extensions are brilliant, but the auto-discovery mechanism is a lot of VERY complicated experimental code.

Now, the simplest thing you can do is just use a custom MVC ControllerFactory that instantiates a new MEF container on each request. That works well, and is trivially easy to implement:

public class MefControllerFactory : IControllerFactory
{
    public IController CreateController(RequestContext requestContext, string controllerName)
    {
        var catalog = new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin"));
        var requestContainer = new CompositionContainer(catalog);
        var controller = requestContainer.GetExportedValue(controllerName);

        if (controller == null){ throw new HttpException(404, "Not found");}
        
        return controller;
    }
}

Sure, this is fine, but it sort of undermines a lot of the power of MEF. MEF's default behavior uses a singleton pattern to reuse parts that have already been instantiated, but this mechanism eliminates ALL reuse, by recombobulating the entire container on each request. It also has an appreciable performance impact since reflection has to go over and build up the entire catalog each time too.

Another solution is to just create an application wide container, and just keep the controllers from being reused by setting the PartCreationPolicy attribute to NonShared. That's a better solution, and simple to achieve too. It looks something like this:

public static class ContainerManager
{
    private static CompositionContainer _container;
    public static CompositionContainer ApplicationContainer
    {
        get
        {
            if (_container == null)
            {
                var catalog = new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin"));
                _container = new CompositionContainer(catalog);
            }
            return _container;
        }
    }
}

Then your controller just uses the application container from this static class. Very simple, and allows you to control reuse using MEF's standard attributes.

I actually recommend the above approach, but it bothered me to mark controllers as NonShared. It isn't that controller instances cannot be reused, it's just that in MVC they can't be reused across multiple requests.
So I came up with a more ghetto solution that can sort-of mimic a FilteredCatalog even in medium trust. This allows for a pattern more similar to the Nerd Dinner MEF sample; you can have application scoped containers, and smaller per-request containers for just for the controllers too.

It looks a little something like this:

First create a class derived from HttpApplication so you can boot-strap creating the MEF containers and catalogs on application startup:

public class MefHttpApplication : HttpApplication
{
    public static ComposablePartCatalog RootCatalog { get; private set; }
    public static CompositionContainer ApplicationContainer { get; private set; }
    public static ComposablePartCatalog ControllerCatalog { get; private set; }

    protected virtual void Application_Start()
    {
        if (RootCatalog == null){ RootCatalog = CreateRootCatalog(); }
        if (ApplicationContainer == null)
        {
            ApplicationContainer = new CompositionContainer(RootCatalog, false);
        }
        if (ControllerCatalog == null)
        {
            var controllerTypes = Assembly.GetExecutingAssembly().GetTypes().Where(t => t.GetInterfaces().Any(i => i == typeof(IController)));
            ControllerCatalog = new TypeCatalog(controllerTypes);
        }
        ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory());
    }

    protected virtual void Application_End()
    {
        if (ApplicationContainer != null){ApplicationContainer.Dispose();}
    }

    protected virtual ComposablePartCatalog CreateRootCatalog()
    {
        return new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin"));
    }
}

On startup we create a master catalog of every part definition, and an application scoped container from that master catalog. But we also create a catalog containing just the controller parts by using a bit of reflection to pull out just controllers and shoving them into a TypeCatalog (which is built-in with MEF)... the poor man's filtered catalog!

Now just doctor up Global.asax to inherit the MefHttpApplication class:

public class MvcApplication : MefHttpApplication
{
    public static void RegisterRoutes(RouteCollection routes)
    {
       //normal route stuff
    }

    protected override void Application_Start()
    {
        base.Application_Start();
        AreaRegistration.RegisterAllAreas();
        RegisterRoutes(RouteTable.Routes);
    }
}

And finally, we need our ControllerFactory:

public class MefControllerFactory : IControllerFactory
{
    public IController CreateController(RequestContext requestContext, string controllerName)
    {
        var requestContainer = GetRequestControllerContainer(requestContext.HttpContext.Items);
        var controller = requestContainer.GetExportedValue(controllerName);

        if (controller == null){throw new HttpException(404, "Not found");}

        return controller;
    }

    public void ReleaseController(IController controller){/*nothing to do*/}

    public static CompositionContainer GetRequestControllerContainer(IDictionary contextItemsCollection)
    {
        var app = (MefHttpApplication)HttpContext.Current.ApplicationInstance;

        if (contextItemsCollection == null) throw new ArgumentNullException("dictionary");

        var container = (CompositionContainer)contextItemsCollection["MefRequestControllerContainer"];

        if (container == null)
        {
            container = new CompositionContainer(MefHttpApplication.ControllerCatalog, false, MefHttpApplication.ApplicationContainer);
            contextItemsCollection["MefRequestControllerContainer"] = container;
        }
        return container;
    }
}

As you can see, the overall technique here is similar to that used in the Nerd Dinner MEF sample. We have a static method that we can call to build a per-request container. It stuffs the entire container into context in case its needed later. The key to the container itself is that it is built from our catalog of just controller types, and uses the application scoped MEF container as an export provider for any other parts the controllers might need to import.

In the long-run, this is probably no better than just marking our controllers as NonShared and using an application wide container, but the general concept of this technique can be applied to other situations besides just dependency injection with controllers. While you can't truly filter catalogs and manipulate part in partial trust, you can still use reflection to create specialized catalogs and achieve similar results... for the simpler cases anyway.

No comments:

Post a Comment