Logo

Maarten Balliauw {blog}

ASP.NET, ASP.NET MVC, Windows Azure, PHP, ...

About the author

Maarten Balliauw is currently employed as a Technical Evangelist at JetBrains. His interests are mainly web applications developed in ASP.NET (C#) or PHP and the Windows Azure cloud platform.
More about me More about me
Send mail E-mail me


ASP.NET MVC Quickly Pro NuGet Subscribe to my RSS feed Follow me on Twitter! View Maarten Balliauw's profile on LinkedIn
Maarten Balliauw - MVP - Most Valuable Professional
Maarten Balliauw - ASPInsider

Search

Archive

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright Maarten Balliauw 2013


ASP.NET MVC and the Managed Extensibility Framework (MEF)

Microsoft’s Managed Extensibility Framework (MEF) is a .NET library (released on CodePlex) that enables greater re-use of application components. You can do this by dynamically composing your application based on a set of classes and methods that can be combined at runtime. Think of it like building an appliation that can host plugins, which in turn can also be composed of different plugins. Since examples say a thousand times more than text, let’s go ahead with a sample leveraging MEF in an ASP.NET MVC web application.

kick it on DotNetKicks.com

Getting started…

The Managed Extensibility Framework can be downloaded from the CodePlex website. In the download, you’ll find the full source code, binaries and some examples demonstrating different use cases for MEF.

Now here’s what we are going to build: an ASP.NET MVC application consisting of typical components (model, view, controller), containing a folder “Plugins” in which you can dynamically add more models, views and controllers using MEF. Schematically:

Sample Application Architecture

Creating a first plugin

Before we build our host application, let’s first create a plugin. Create a new class library in Visual Studio, add a reference to the MEF assembly (System.ComponentModel.Composition.dll) and to System.Web.Mvc and System.Web.Abstractions. Next, create the following project structure:

Sample Plugin Project

That is right: a DemoController and a Views folder containing a Demo folder containing Index.aspx view. Looks a bit like a regular ASP.NET MVC application, no? Anyway, the DemoController class looks like this:

[Export(typeof(IController))]
[ExportMetadata("controllerName", "Demo")]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class DemoController : Controller
{
    public ActionResult Index()
    {
        return View("~/Plugins/Views/Demo/Index.aspx");
    }
}

Nothing special, except… what are those three attributes doing there, Export and PartCreationPolicy? In short:

  • Export tells the MEF framework that our DemoController class implements the IController contract and can be used when the host application is requesting an IController implementation.
  • ExportMetaData provides some metadata to the MEF, which can be used to query plugins afterwards.
  • PartCreationPolicy tells the MEF framework that it should always create a new instance of DemoController whenever we require this type of controller. By defaukt, a single instance would be shared across the application which is not what we want here. CreationPolicy.NonShared tells MEF to create a new instance every time.

Now we are ready to go to our host application, in which this plugin will be hosted.

Creating our host application

The ASP.NET MVC application hosting these plugin controllers is a regular ASP.NET MVC application, in which we’ll add a reference to the MEF assembly (System.ComponentModel.Composition.dll). Next, edit the Global.asax.cs file and add the following code in Application_Start:

ControllerBuilder.Current.SetControllerFactory(
    new MefControllerFactory(
        Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins")));

What we are doing here is telling the ASP.NET MVC framework to create controller instances by using the MefControllerFactory instead of ASP.NET MVC’s default DefaultControllerFactory. Remember that everyone’s always telling ASP.NET MVC is very extensible, and it is: we are now changing a core component of ASP.NET MVC to use our custom MefControllerFactory class. We’re also telling our own MefControllerFactory class to check the “Plugins” folder in our web application for new plugins. By the way, here’s the code for the MefControllerFactory:

public class MefControllerFactory : IControllerFactory
{
    private string pluginPath;
    private DirectoryCatalog catalog;
    private CompositionContainer container;

    private DefaultControllerFactory defaultControllerFactory;

    public MefControllerFactory(string pluginPath)
    {
        this.pluginPath = pluginPath;
        this.catalog = new DirectoryCatalog(pluginPath);
        this.container = new CompositionContainer(catalog);

        this.defaultControllerFactory = new DefaultControllerFactory();
    }

    #region IControllerFactory Members

    public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
    {
        IController controller = null;

        if (controllerName != null)
        {
            string controllerClassName = controllerName + "Controller";
            Export<IController> export = this.container.GetExports<IController>()
                                             .Where(c => c.Metadata.ContainsKey("controllerName")
                                                 && c.Metadata["controllerName"].ToString() == controllerName)
                                             .FirstOrDefault();
            if (export != null) {
                controller = export.GetExportedObject();
            }
        }

        if (controller == null)
        {
            return this.defaultControllerFactory.CreateController(requestContext, controllerName);
        }

        return controller;
    }

    public void ReleaseController(IController controller)
    {
        IDisposable disposable = controller as IDisposable;
        if (disposable != null)
        {
            disposable.Dispose();
        }
    }

    #endregion
}

Too much? Time for a breakdown. Let’s start with the constructor:

public MefControllerFactory(string pluginPath)
{
    this.pluginPath = pluginPath;
    this.catalog = new DirectoryCatalog(pluginPath);
    this.container = new CompositionContainer(catalog);

    this.defaultControllerFactory = new DefaultControllerFactory();
}

In the constructor, we are storing the path where plugins can be found (the “Plugins” folder in our web application). Next, we are telling MEF to create a catalog of plugins based on what it can find in that folder using the DirectoryCatalog class. Afterwards, a CompositionContainer is created which will be responsible for matching plugins in our application.

Next, the CreateController method we need to implement for IControllerFactory:

public IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
{
    IController controller = null;

    if (controllerName != null)
    {
        string controllerClassName = controllerName + "Controller"
        Export<IController> export = this.container.GetExports<IController>()
                                         .Where(c => c.Metadata.ContainsKey("controllerName"
                                             && c.Metadata["controllerName"].ToString() == controllerName)
                                         .FirstOrDefault();
        if (export != null) {
            controller = export.GetExportedObject();
        }
    }

    if (controller == null)
    {
        return this.defaultControllerFactory.CreateController(requestContext, controllerName);
    }

    return controller;
}

This method handles the creation of a controller, based on the current request context and the controller name that is required. What we are doing here is checking MEF’s container for all “Exports” (plugins as you wish) that match the controller name. If one is found, we return that one. If not, we’re falling back to ASP.NET MVC’s DefaultControllerBuilder.

The ReleaseController method is not really exciting: it's used by ASP.NET MVC to correctly dispose a controller after use.

Running the sample

First of all, the sample code can be downloaded here: MvcMefDemo.zip (270.82 kb)

When launching the application, you’ll notice nothing funny. That is, untill you want to navigate to the http://localhost:xxxx/Demo URL: there is no DemoController to handle that request! Now compile the plugin we’ve just created (in the MvcMefDemo.Plugins.Sample project) and copy the contents from the \bin\Debug folder to the \Plugins folder of our host application. Now, when the application restarts (for example by modifying web.config), the plugin will be picked up and the http://localhost:xxxx/Demo URL will render the contents from our DemoController plugin:

Sample run MEF ASP.NET MVC

Conclusion

The MEF (Managed Extensibility Framework) offers a rich manner to dynamically composing applications. Not only does it allow you to create a plugin based on a class, it also allows exporting methods and even properties as a plugin (see the samples in the CodePlex download).

By the way, sample code can be downloaded here: MvcMefDemo.zip (270.82 kb)

kick it on DotNetKicks.com


Categories: ASP.NET | C# | General | MEF | MVC | Quality code

Comments (24) -

runxc United States |

Tuesday, April 21, 2009 4:31 PM

runxc

MEF looks very promising.  It seems that extensibility is something that we are all trying to achieve.  I love the article but find myself wanting more...  How does this work in say a standard ASP.Net environment or say you have a web site using Web parts.   How could you add or remove these web parts or widgets dynamically.  

Maarten Balliauw Belgium |

Tuesday, April 21, 2009 10:20 PM

Maarten Balliauw

Are you saying to, for example, let MEF fill up your web part catalog? If so, let me know and I'l lcheck if I can do a post on something like that.

huey |

Tuesday, April 21, 2009 4:34 PM

huey

So you have to give explicit views due to it being in the plugins directory yes?  Although that is pretty simple to customize too.

hammett Canada |

Tuesday, April 21, 2009 8:13 PM

hammett

Hi, awesome work! Thanks for sharing it. Just a note: your implementation of ReleaseController should call container.ReleaseExport( controllerExport ) instead. This way MEF can release references and dispose any nonshared/disposable part in the graph that the controller started.

I will publish a sample on MEF/MVC this week that shows how to do that. Stay tuned Smile

Thanks

Maarten Balliauw Belgium |

Tuesday, April 21, 2009 10:35 PM

Maarten Balliauw

Let me know the link to your post? I'll update my code to reflect your tips. Thanks!

hammett Canada |

Tuesday, April 21, 2009 8:32 PM

hammett

Also, c => c.GetExportedObject() is very bad Smile  
You're constructing all controllers (and their dependency chain). The best approach would be to associate a metadata with the controller, or use a custom catalog for controllers.

Maarten Balliauw Belgium |

Tuesday, April 21, 2009 10:36 PM

Maarten Balliauw

Agree with that... THought the non-metadata approach would be clearer for a blog post, but then again... metadata is better. Thanks for the tip!

Daniel Earwicker United Kingdom |

Thursday, May 14, 2009 10:43 AM

Daniel Earwicker

Not necessarily bad - depends where you put it in the chain of linq operators. If you put it after the Where but before the FirstOrDefault, you could assign the result of the expression directly to 'controller', and miss out the test for export != null (lose the export variable altogether). This would be equivalent to your current code but all in a single expression, and would only construct one controller, not all of them.

Jeff United States |

Tuesday, April 21, 2009 11:44 PM

Jeff

very cool, I like it alot.  I have been working on somthing like this,  it helps alot.  Is it possible to say be able to update these "plug-ins" on the fly.  So from my app a can download a new version from some sort of online repository and have it overwrit the one in the plugin directory.  It was always a problem with like assembly.load and what not.  but i am not sure if mef resolves this.

maartenba Belgium |

Wednesday, April 22, 2009 8:11 AM

maartenba

That should work, you'll have to issue a catalog.Refresh() in code though. Also, I've noticed the ASP.NET devserver locks the plugin dll's which makes it a bit difficult to overwrite while running. But in theory, it should work.

Jeff United States |

Friday, April 24, 2009 6:17 PM

Jeff

catalog.refresh() I can handle it.  The locks is what i was really wondering about.  Any tips, before I just dive in and start hitting walls?

ringo Italy |

Wednesday, April 22, 2009 11:43 AM

ringo

Great article!
What about including the views as a embedded resource in the plugin dll and then create a route to find them?
What's the impact on performances?

maartenba Belgium |

Wednesday, April 22, 2009 11:47 AM

maartenba

That is another option, true. Not sure about performance though.

hammett Canada |

Thursday, April 23, 2009 8:03 PM

hammett

Sure, here it goes
blogs.msdn.com/.../mef-and-asp-net-mvc-sample.aspx

maartenba Belgium |

Friday, April 24, 2009 7:32 AM

maartenba

Great post!

Jack United States |

Saturday, April 25, 2009 3:24 AM

Jack

Nice article, 'KICK' it!

maartenba Belgium |

Monday, April 27, 2009 3:37 PM

maartenba

Another tip: you can use this in conjunction with www.wynia.org/.../ to embed your views as an embedded resource.

Felix Italy |

Tuesday, June 16, 2009 2:28 PM

Felix

Great Post!
Just a question:
How about customize the navigation Menu to add new link for each Controller?

maartenba Netherlands |

Wednesday, June 17, 2009 10:25 AM

maartenba

You can use http://mvcsitemap.codeplex.com for that and dynamically add MvcSiteMapNodes. Or create your own navigation handler which also queries MEF for certain exports.

Tom United States |

Thursday, November 19, 2009 7:14 AM

Tom

Hi.  Thank you for your article and introduction to MEF for MVC.  I noticed the views in your plugin are not strongly typed.  I tried modifying your code to make your demo Index.aspx view strongly typed and it failed.  Can you make views in the plugins strongly typed as opposed to passing data to the view via ViewModel dictionary?  Thanks

maartenba Belgium |

Thursday, November 19, 2009 9:41 PM

maartenba

Check blog.maartenballiauw.be/.../...ramework-(MEF).aspx - it's not the prettiest workaround but It will make strongly typed views work.

Tom United States |

Thursday, November 19, 2009 10:55 PM

Tom

Thank you for the reply.  I downloaded your example and it works.  However, I noticed your view code does not actually display any data from the strongly typed model.  I went to augment the view to display the DemoProperty element from  MvcMefDemo.Plugins.Sample.Models.DemoModel.  I was expecting to do this:

<%= Model.DemoProperty %>

but Model is not an option in Intellisense.  I then put the code back like it was, recompiled, then copied new Views folder and generated DLLs from MvcMefDemo.Plugins.Sample/bin/debug to MvcMefDemo.Web/Plugins.  I reloaded the Demo page in the browser and got :

Could not load 'System.Web.Mvc.ViewPage<MvcMefDemo.Plugins.Sample.Models.DemoModel>

So, I am not sure how to proceed.  Any ideas?  Thanks

Tom

Rob United Kingdom |

Thursday, May 13, 2010 8:30 PM

Rob

Could this be done using a similar method to:

www.hanselman.com/.../CommentView.aspx

Dea United States |

Monday, November 15, 2010 3:28 PM

Dea

Hi maartenba,
I am trying to use your implementation with mvc 3.0 there is new method in IControllerFactory
public SessionStateBehavior GetControllerSessionBehavior(RequestContext requestContext, string controllerName)
may be you may give some advice how to implement it.

Pingbacks and trackbacks (7)+

Comments are closed