MVC4 MEF Namespaces and Controllers

I am trying to create an MVC4 web application using several plugins, that is, essentially, controllers exported via MEF files plus files unpacked to the appropriate places. I found a lot of material about MVC plugins, mainly related to areas, but I had to abandon MvcContrib, which would be the most obvious solution, since it seems to be no more developed, shows some problems with the last bits of MVC, and I also need minimally complex implementation for this architecture.

My requirements were this way:

a) an MEF-based MVC solution in which I simply drop the package on my site so that it can be used, ideally, even without a reboot. This implies storing plugins in a folder other than Bin, which also provides better isolation.

b) a solution compatible with IoC tools is more complete than what can only be done by MEF. I tend to use Autofac for this, as it has integration with MEF and MVC4 (RC at this time).

Besides content similar to views, an important task for this is to allow MVC to find the controllers among the MEF plugins and create them, so I need a factory controller. I found a good article about this here: http://kennytordeur.blogspot.be/2012/08/mef-in-aspnet-mvc-4-and-webapi.html (I contacted Kenny about this and I thank him for that he pointed out to me some kind of routing problem), the Author also wrapped his code in a convenient nuget package (MEF.MVC4). In any case, I found a problem with routing and namespaces: when removing the route to the plugin controller, the MEF factory controller GetControllerInstance gets the controller type zero , which ultimately leads to 404. I think that maybe I found the culprit after reading these posts:

http://blog.davebouwman.com/2011/12/08/asp-net-mvc3-and-404s-for-area-controllers/

and

Factory user controller, dependency / structure injection problems using ASP.NET MVC

I suppose (but I could be wrong) the problem is with the routing conventions and the controller spaces of the plugin controllers: the namespace of the plugin controller is not in the same "root" of the host network. The solution proposed in the message simply adds a new route to the web application, but it does not fit into the solution, in which the areas work as plugins, are dynamically added to the host application. My home web application should not be aware of plugins, and this, of course, should be a fairly common requirement, but I do not find an obvious solution for this.

Repro Solution

You can quickly create a playback solution to view the details of my approach by following these steps or downloading it from here :

1) create an empty solution.

2) create the MVC4 web application (HostWeb) in it, update all the pre-installed NuGet packages and add Mef.MVC4, Autofac MVC 4 (RC) and Autofac.Mef. In my real world application, I would like to use Autofac to set dispatcher dependencies in the constructor.

3) create the Plugins folder in HostWeb and the Temp subfolder. This will include plugins using the Temp subfolder as a shadow copy container so that the MEF directory is loaded from it, and not directly from the plugins. This, combined with some startup code, should allow me to update the plugins without reloading the web application (which would otherwise block the DLLs). The startup code is a class called PreApplicationInit, which you can find in the Infrastructure folder (slightly modified from http://shazwazza.com/post/Developing-a-plugin-framework-in-ASPNET-with-medium-trust. aspx ).

4) add the details area to the host website and copy the _ViewStart file from the root folder of the views into it (and change the existing links in the layout view so that the empty area is added to the route values ​​so that they do not break). All plugin controllers will be placed in a zone named Parts. The host web application has such an area without a controller, just to prepare the folder structure and routes for the plug-in content files (views that will be unpacked from the plug-in installer module to the desired location, and binary files will be placed in the plug-ins).

5) in App_Start HostWeb configure MefConfig and add IocConfig, which deals with Autofac. Then add to the global asax calls for both: MefConfig.RegisterMef () and IocConfig.RegisterDependencies ().

6) create another MVC4 web application (AlphaPlugin) in it, update all the pre-installed NuGet packages and add Autofac MVC 4 (RC) and Autofac.Mef. I choose a web application template (not a class library), so I can use all the features of VS for MVC and, ultimately, run some tests directly there.

7) add the details area to the host website and copy the _ViewStart file from the views root folder into it.

8) add the exported controller to the "Parts" area. Mine is called AlphaController and has only an action method called Hail, which places the string in the ViewBag string and returns the default view.

9) back to the host, just add a link to the action of the plug-in controller in your home view to check that it is available through MEF.

Now, if I create everything and copy the binary AlphaPlugin.dll to the HostWeb Plugins folder, I expect MVC to find it through MEF, but then throw an error not found in the view, since I have not copied the content files to the web host yet . Instead, I get the following:

Value cannot be null. Parameter name: type Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. Exception Details: System.ArgumentNullException: Value cannot be null. Parameter name: type Source Error: An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below. Stack Trace: [ArgumentNullException: Value cannot be null. Parameter name: type] System.ComponentModel.Composition.Hosting.ExportProvider.GetExportsCore(Type type, Type metadataViewType, String contractName, ImportCardinality cardinality) +263923 System.ComponentModel.Composition.Hosting.ExportProvider.GetExports(Type type, Type metadataViewType, String contractName) +41 MEF.MVC4.MefControllerFactory.GetControllerInstance(RequestContext requestContext, Type controllerType) +84 System.Web.Mvc.DefaultControllerFactory.CreateController(RequestContext requestContext, String controllerName) +226 System.Web.Mvc.MvcHandler.ProcessRequestInit(HttpContextBase httpContext, IController& controller, IControllerFactory& factory) +326 System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +177 System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state) +88 System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) +50 System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +301 System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155 

Here is the most appropriate code (you can find all of this in the playback solution): this concerns the contents of the Plugins folders so that the web application downloads them from a copy:

 [assembly: PreApplicationStartMethod(typeof(PreApplicationInit), "Initialize")] static public class PreApplicationInit { /// /// The source plugin folder from which to shadow copy from. /// /// This folder can contain sub folders to organize plugin types. internal static DirectoryInfo PluginFolder { get; private set; } /// /// The folder to shadow copy the plugin DLLs to use for running the app. /// internal static DirectoryInfo ShadowCopyFolder { get; private set; } static PreApplicationInit() { PluginFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/Plugins")); ShadowCopyFolder = new DirectoryInfo(HostingEnvironment.MapPath("~/Plugins/Temp")); } public static void Initialize() { if (!Directory.Exists(ShadowCopyFolder.FullName)) Directory.CreateDirectory(ShadowCopyFolder.FullName); else { foreach (FileInfo fi in ShadowCopyFolder.GetFiles("*.dll", SearchOption.AllDirectories)) { try { fi.Delete(); } catch (Exception ex) { // TODO log Debug.WriteLine(ex.ToString()); } } } // shadow copy files foreach (FileInfo fi in PluginFolder.GetFiles("*.dll")) { try { File.Copy(fi.FullName, Path.Combine(ShadowCopyFolder.FullName, fi.Name), true); } catch (Exception ex) { // TODO log Debug.WriteLine(ex.ToString()); } } } } 

And this is my Factory, which in any case gets the controller type zero, so its code is never executed outside of its first line:

 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) { // /questions/189763/custom-controller-factory-dependency-injection-structuremap-problems-with-aspnet-mvc if (controllerType == null) return base.GetControllerInstance(requestContext, null); var export = _compositionContainer.GetExports(controllerType, null, null).SingleOrDefault(); IController result; if (export != null) result = export.Value as IController; else { result = base.GetControllerInstance(requestContext, controllerType); _compositionContainer.ComposeParts(result); } return result; } 
+8
asp.net-mvc-4 autofac mef asp.net-mvc-areas
source share
1 answer

The factory controller needs a hand to determine the correct controller type for external MEF components. Override the GetControllerType method of the MefControllerFactory class.

 public class MefControllerFactory : DefaultControllerFactory { protected override Type GetControllerType(RequestContext requestContext, string controllerName) { var controllerType = base.GetControllerType(requestContext, controllerName); if (controllerType == null) { var controller = _compositionContainer.GetExports<IController, IControllerMetaData>().SingleOrDefault(x => x.Metadata.ControllerName == controllerName).Value; if (controller != null) { return controller.GetType(); } } return controllerType; } } 

Where IControllerMetaData is the interface that indicates the name of the controller

 public interface IControllerMetaData { string ControllerName { get;} } 

And your controller indicates the name of the controller in the metadata. For example.

 [Export (typeof(IController))] [ExportMetadata("ControllerName", "Home")] [PartCreationPolicy(CreationPolicy.NonShared)] public class HomeController : Controller, IController { public ActionResult Index() { return new EmptyResult(); } } 
+1
source share

All Articles