29

How does one integrate Managed Extensibility Framework (MEF) with ASP.NET MVC 4 and ASP.NET Web API in the same project?

Consider an example application, with an MVC controller HomeController and a Web API controller ContactController. Both have a property of type IContactRepository, which they rely on MEF to resolve. The problem is how to plug MEF into MVC and Web API, so that instances are created via MEF.

HomeController:

/// <summary>
/// Home controller. Instruct MEF to create one instance of this class per importer,
/// since this is what MVC expects.
/// </summary>
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class HomeController : Controller
{
    [Import]
    private IContactRepository _contactRepository = null;

    public ActionResult Index()
    {
        return View(_contactRepository.GetAllContacts());
    }
}

ContactController:

/// <summary>
/// Contact API controller. Instruct MEF to create one instance of this class per importer,
/// since this is what Web API expects.
/// </summary>
[Export]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ContactController : ApiController
{
    [Import]
    private IContactRepository _contactRepo = null;

    public Contact[] Get()
    {
        return _contactRepo.GetAllContacts();
    }
}

IContactRepository and ContactRepository:

public interface IContactRepository
{
    Contact[] GetAllContacts();
}

[Export(typeof(IContactRepository))]
public class ContactRepository : IContactRepository
{
    public Contact[] GetAllContacts()
    {
        return new Contact[] {
            new Contact { Id = 1, Name = "Glenn Beck"},
            new Contact { Id = 2, Name = "Bill O'Riley"}
        };
    }
}

Contact:

public class Contact
{
    public int Id { get; set; }
    public string Name { get; set; }
}
raistlin0788
  • 406
  • 4
  • 12
aknuds1
  • 65,625
  • 67
  • 195
  • 317

6 Answers6

31

The solution is to implement System.Web.Mvc.IDependencyResolver and System.Web.Http.Dependencies.IDependencyResolver and register your implementation with ASP.NET MVC and ASP.NET Web API respectively, in your Application_Start method.

In this example we'll create a class MefConfig, which implements a method RegisterMef that gets called from Application_Start in order to install our dependency resolver. The class MefDependencyResolver implements both System.Web.Mvc.IDependencyResolver and System.Web.Http.Dependencies.IDependencyResolver and, as such, handles dependency resolution duties for both MVC and Web API.

Application_Start, Put This in Your Global.asax.cs:

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        [...]
        MefConfig.RegisterMef();
    }
}

MefDependencyResolver and MefConfig:

/// <summary>
/// Resolve dependencies for MVC / Web API using MEF.
/// </summary>
public class MefDependencyResolver : System.Web.Http.Dependencies.IDependencyResolver, System.Web.Mvc.IDependencyResolver
{
    private readonly CompositionContainer _container;

    public MefDependencyResolver(CompositionContainer container)
    {
        _container = container;
    }

    public IDependencyScope BeginScope()
    {
        return this;
    }

    /// <summary>
    /// Called to request a service implementation.
    /// 
    /// Here we call upon MEF to instantiate implementations of dependencies.
    /// </summary>
    /// <param name="serviceType">Type of service requested.</param>
    /// <returns>Service implementation or null.</returns>
    public object GetService(Type serviceType)
    {
        if (serviceType == null)
            throw new ArgumentNullException("serviceType");

        var name = AttributedModelServices.GetContractName(serviceType);
        var export = _container.GetExportedValueOrDefault<object>(name);
        return export;
    }

    /// <summary>
    /// Called to request service implementations.
    /// 
    /// Here we call upon MEF to instantiate implementations of dependencies.
    /// </summary>
    /// <param name="serviceType">Type of service requested.</param>
    /// <returns>Service implementations.</returns>
    public IEnumerable<object> GetServices(Type serviceType)
    {
        if (serviceType == null)
            throw new ArgumentNullException("serviceType");

        var exports = _container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
        return exports;
    }

    public void Dispose()
    {
    }
}

public static class MefConfig
{
    public static void RegisterMef()
    {
        var asmCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
        var container = new CompositionContainer(asmCatalog);
        var resolver = new MefDependencyResolver(container);
        // Install MEF dependency resolver for MVC
        DependencyResolver.SetResolver(resolver);
        // Install MEF dependency resolver for Web API
        System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver = resolver;
    }
}
aknuds1
  • 65,625
  • 67
  • 195
  • 317
  • 2
    Really works, thanks. For me I've just changed catalog a little bit: var catalog = new AggregateCatalog(); catalog.Catalogs.Add(new DirectoryCatalog(@"..\EducationBusinessLogic\bin\Debug", "*")); catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly())); – Arsen Khachaturyan Apr 28 '14 at 10:45
  • 4
    Unfortunately, the suggested solution leads to memory leaks when used for non-shared `IDisposable` service types such as `ApiController`s because of http://stackoverflow.com/questions/8787982/mef-keeps-reference-of-nonshared-idisposable-parts-not-allowing-them-to-be-coll. I wonder how people get around this. – Dejan Sep 07 '15 at 12:14
  • 1
    FWIW, I found that [Import]s are not resolved in the ApiController class unless the ApiController class is itself exported with an [Export] attribute above the class declaration. – ThisIsTheDave Nov 22 '17 at 23:16
  • 1
    @Dejan I've fixed this issue and posted it as an answer below - have a look. – Adi Lester Mar 21 '19 at 08:25
2

@aknuds1 answer is the best I've seen so far for integrating MEF into the DependencyResolver. I was able to extend it to use the convention-based composition in MEF2 fairly easily. The MefConfig class is all that needed to change and then not by much.

/// <summary>
///     Responsible for configuring MEF for the application.
/// </summary>
public static class MefConfig
{
    /// <summary>
    ///     Registers MEF conventions and exports.
    /// </summary>
    public static void RegisterMef()
    {
        // Register MVC/API conventions
        var registrationBuilder = new RegistrationBuilder();
        registrationBuilder.ForTypesDerivedFrom<Controller>().SetCreationPolicy(CreationPolicy.NonShared).Export();
        registrationBuilder.ForTypesDerivedFrom<ApiController>().SetCreationPolicy(CreationPolicy.NonShared).Export();
        var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly(), registrationBuilder);
        var aggregateCatalog = new AggregateCatalog(assemblyCatalog);
        var container = new CompositionContainer(aggregateCatalog);
        var resolver = new MefDependencyResolver(container);
        // Install MEF dependency resolver for MVC
        DependencyResolver.SetResolver(resolver);
        // Install MEF dependency resolver for Web API
        GlobalConfiguration.Configuration.DependencyResolver = resolver;
    }
}
Robb Vandaveer
  • 1,481
  • 20
  • 25
  • I feel compelled to comment, since someone voted up my answer here that there is a flaw and that is that you will get memory and connection leaks using the accepted answer. I have posted another question related to this which as of yet, there are no answers: http://stackoverflow.com/questions/20806239/how-to-properly-scope-composition-per-request-using-asp-net-mvc-webapi-and-mef. I have written my own solution which I will add to my question soon, especially if no one offers up another solution. – Robb Vandaveer Jan 10 '14 at 14:44
1

You can take a look at this http://kennytordeur.blogspot.be/2012/08/mef-in-aspnet-mvc-4-and-webapi.html. It explains how to use MEF in an Asp.net MVC 4/Web Api project. There also exists a Nuget package based on this code. That way you can test it very easily and quickly.

Kenny Tordeur
  • 212
  • 1
  • 10
  • 3
    I looked at your blog post before coming up with my answer. I think the use of DefaultControllerFactory with MVC is now an outdated technique though, and one [should implement IDependencyResolver instead](http://weblogs.asp.net/shijuvarghese/archive/2011/01/21/dependency-injection-in-asp-net-mvc-3-using-dependencyresolver-and-controlleractivator.aspx). Besides, just linking to an external solution in an SO answer is best avoided. – aknuds1 Dec 13 '12 at 08:17
  • Seems it's still there - but if it ever disappears again you can find at it https://web.archive.org/web/20180507160323/http://kennytordeur.blogspot.rs/2012/08/mef-in-aspnet-mvc-4-and-webapi.html – Mladen Mihajlovic May 07 '18 at 16:04
1

@aknuds1's solution works, but leaks memory on every API call. I've modified his solution to fix the leak.

public class MefDependencyResolver : System.Web.Http.Dependencies.IDependencyResolver, System.Web.Mvc.IDependencyResolver
{
    private readonly CompositionContainer container;
    private readonly List<Lazy<object, object>> exports = new List<Lazy<object, object>>();
    private readonly object syncRoot = new object();

    public MefDependencyResolver(CompositionContainer container)
    {
        this.container = container;
    }

    public IDependencyScope BeginScope()
    {
        return new MefDependencyResolver(container);
    }

    /// <summary>
    /// Called to request a service implementation.
    /// 
    /// Here we call upon MEF to instantiate implementations of dependencies.
    /// </summary>
    /// <param name="serviceType">Type of service requested.</param>
    /// <returns>Service implementation or null.</returns>
    public object GetService(Type serviceType)
    {
        if (serviceType == null) throw new ArgumentNullException(nameof(serviceType));

        var serviceExport = container.GetExports(serviceType, null, null).FirstOrDefault();
        if (serviceExport == null) return null;

        lock (this.syncRoot)
        {
            exports.Add(serviceExport);
        }

        return serviceExport.Value;

    }

    /// <summary>
    /// Called to request service implementations.
    /// 
    /// Here we call upon MEF to instantiate implementations of dependencies.
    /// </summary>
    /// <param name="serviceType">Type of service requested.</param>
    /// <returns>Service implementations.</returns>
    public IEnumerable<object> GetServices(Type serviceType)
    {
        if (serviceType == null) throw new ArgumentNullException(nameof(serviceType));

        var serviceExports = container.GetExports(serviceType, null, null);
        if (!serviceExports.Any()) return Enumerable.Empty<object>();

        lock (this.syncRoot)
        {
            exports.AddRange(serviceExports);
        }

        return serviceExports.Select(x => x.Value);
    }

    public void Dispose()
    {
        lock (this.syncRoot)
        {
            foreach (var e in exports)
            {
                this.container.ReleaseExport(e);
            }

            exports.Clear();
        }
    }
}
Adi Lester
  • 24,731
  • 12
  • 95
  • 110
0

This is a simpler approach that I'm using in my MVC4 project.

public static class MefConfig
{
     public static CompositionContainer MefContainer = null;

     public static void Initialise()
     {
          AggregateCatalog cat = new AggregateCatalog();
          cat.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
          MefContainer = new CompositionContainer(cat);
      }
}

public class MefFilterAttribute : ActionFilterAttribute
{
   public override void OnActionExecuting(ActionExecutingContext filterContext)
   {
      MefConfig.MefContainer.ComposeParts(filterContext.Controller);
   }        
}

In Application_Start run MefConfig.Initialise() and in FilterConfig.RegisterGlobalFilters(GlobalFilterCollection filters) put filters.Add(new Filters.MefFilterAttribute());

Stuntbeaver
  • 690
  • 7
  • 8
0

I followed @akanuds1's answer but I also had to change the ControllerFactory to this:

public class MefControllerFactory : DefaultControllerFactory
{
    private readonly CompositionContainer compositionContainer;

    public MefControllerFactory(CompositionContainer compositionContainer)
    {
        this.compositionContainer = compositionContainer;
    }

    protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
    {
        var export = compositionContainer.GetExports(controllerType, null, null).SingleOrDefault();

        IController result;

        if (null != export)
        {
            result = export.Value as IController;
        }
        else
        {
            result = base.GetControllerInstance(requestContext, controllerType);
            compositionContainer.ComposeParts(result);
        }

        return result;
    }
}

Glogal.asax.cs

protected void Application_Start()
{
    ...
    var container = MefConfig.Register();
    ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory(container));
}
BrunoLM
  • 97,872
  • 84
  • 296
  • 452