Asp.Net MVC 4
First of all you need to reference the “System.ComponentModel.Composition.dll” assembly which contains the implementation of MEF. Now where are going to create a MEF controller factory. Like the name says, it will be responsible to create the controller that is requested.public class MefControllerFactory : DefaultControllerFactory { private readonly CompositionContainer _compositionContainer; public MefControllerFactory(CompositionContainer compositionContainer) { _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; } }
The MefcontrollerFactory will inherit from the DefaultControllerFactory and has a constructor that accepts a CompositionContainer. To plug in MEF, the GetControllerInstance methode will be overridden. First will look in our exports if something is defined for the requested controller type. Mark that the SingleOrDefault method is used because if more exports are defined for a controller, an exception will be throw. If this exception is thrown, something is wrong with the configuration of the CompositionContainer. If a single export is found, we’ll get the object from the Value property.
If the export isn’t found, the base method will be invoked. That way, the controller is created the default-way. When the controller is created, the ComposeParts method on the CompositionContainer is invoked. This will resolve the needed import for the controller.
This is the object that will be injected. The GetMessage method will return a string that will be shown on the view.
public interface IMyTest{ String GetMessage(); } [Export(typeof(IMyTest))] public class MyTest1 : IMyTest{ public MyTest1() { creationDate = DateTime.Now; } public string GetMessage() { return String.Format("MyTest1 created at {0}", creationDate.ToString("hh:mm:ss")) ; } private DateTime creationDate; }
The Export attribute says to MEF that this class can be exported. The type of IMyTest is pasted as a parameter. This indicates the contract that is used. So when an IMyTest is requested from MEF, an object of MyTest1 is returned.
[Export] public class HomeController : Controller{ [Import] private IMyTest _myTest; public ActionResult Index() { ViewBag.Message = _myTest.GetMessage(); return View(); } }
The HomeController is marked for export and an import attribute is put on the “_mytest” property. Of course we still need to configure MVC to use our MefControllerFactorty. To accomplish this, is created a static class with one static public method (RegisterMef). This method will create a Mef CompositionContainer and pass this container to the MefControllerFactory. This MefControllerFactory will be set as the controller factory for MVC. To configure the CompositionContainer an AssemblyCatalog is used.
public static class MefConfig{ public static void RegisterMef() { var container = ConfigureContainer(); ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory(container)); var dependencyResolver = System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver; } private static CompositionContainer ConfigureContainer() { var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(assemblyCatalog); return container; } }
The RegisterMef method will be called form the Application_Start method that resides in the global.asax.
public class MvcApplication : System.Web.HttpApplication{ protected void Application_Start() { AreaRegistration.RegisterAllAreas(); MefConfig.RegisterMef(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); AuthConfig.RegisterAuth(); } }
When the Index action for the HomeController is requested through the browser, everything works just fine but when it is requested it again, an exception is thrown.
A single instance of controller 'MvcApplication5.Controllers.HomeController' cannot be used to handle multiple requests. If a custom controller factory is in use, make sure that it creates a new instance of the controller for each request.
This exception is thrown because the lifetime of the controller is linked to the lifetime of the CompositionContainer. The container keeps a reference to all the objects that it has created. If the same object is requested, the reference of the previous object is given. There are 2 ways to solve this problem.
You could pass an instance of a ComposablePartCatalog (in this case the AssemblyCatalog) to the MefControllerFactorty. Then in the “GetControllerInstance” method always compose a new CompositionContainer to resolve the controller. Personally I'm not really found about that.
The other solution is to use a CreatonPolicy. If we put a CreationPolicy of “NonShared” on the HomeController, a new instance of the HomeController is created from scratch every time it gets requested. Since this a standard feature of MEF, I'll use this solution.
[Export] [PartCreationPolicy(CreationPolicy.NonShared)] public class HomeController : Controller{ [Import] private IMyTest _myTest; public ActionResult Index() { ViewBag.Message = _myTest.GetMessage(); return View(); } }
Now we can request the controller as many times as we want.
WebApi
This is the created WebApi controller. Mark that the controller also has a CreationPolicy defined. This is for the same reason a mentioned above.
[Export] [PartCreationPolicy(CreationPolicy.NonShared)] public class HomeController : ApiController{ [Import] private IMyTest _myTest; public String GetMessage() { return _myTest.GetMessage(); } }
To plug in MEF in the WebApi a object needs to be created from the System.Web.Http.Dependencies.IDependencyResolver interface. This is how the object looks like.
public class MefDependencyResolver : IDependencyResolver{ private readonly CompositionContainer _container; public MefDependencyResolver(CompositionContainer container) { _container = container; } public IDependencyScope BeginScope() { return this; } public object GetService(Type serviceType) { var export = _container.GetExports(serviceType, null, null).SingleOrDefault(); return null != export ? export.Value : null; } public IEnumerable<object> GetServices(Type serviceType) { var exports =_container.GetExports(serviceType, null, null); var createdObjects = new List<object>(); if ( exports.Any()) { foreach (var export in exports) { createdObjects.Add(export.Value); } } return createdObjects; } public void Dispose() { ; } }
The “BeginScope” method returns a scope in with the create objects will life. Since we are going to use MEF to control the lifetime of the created objects, we can do a return of “this”. We can do this because the IDependencyResolver inherits form IDependencyScope. If you really need a limited scope per request, the “BeginScope” method always needs to return a new object of type IDependencyScope.
You also need to implement a “Dispose” method to release the used resources. In the MefDependencyResolver, it isn’t implemented. This is because we return the current instance in the “BeginScope” method. If we disposed the CompositionContainer, we could only request the service once.
Right now, the only thing needed, is the register the create MefDependencyResolver. This will be done in the modified MefConfig class.
public static class MefConfig { public static void RegisterMef() { var container = ConfigureContainer(); ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory(container)); var dependencyResolver = System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver; System.Web.Http.GlobalConfiguration.Configuration.DependencyResolver = new MefDependencyResolver(container); } private static CompositionContainer ConfigureContainer() { var assemblyCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly()); var container = new CompositionContainer(assemblyCatalog); return container; } }
The example can be downloaded from here.