Extending NerdDinner: Adding MEF and plugins to ASP.NET MVC
The original NerdDinner Sample was very simple. Two samples, simple, in fact. Perhaps it's the new Northwind, as it's a good way to start working with ASP.NET MVC. However, it's not a perfect sample or an idealized example on how to do many things that users want to do.
Fortunately, there's been lots of cool folks in the community who have "forked" NerdDinner and done interesting stuff with it. Each of these samples is usually focused on a specific scenario, so they won't necessarily be merged with the trunk, but they are educational nonetheless.
Jon Galloway and I have also added a few things to NerdDinner, taking it in a more social direction, as Jon's MVC Music Store today is a better "getting started" sample for ASP.NET MVC 2. We'll be doing a series of posts on the interesting things the community has added to NerdDinner as well as some of the ones Jon and I added and presented at Mix a few months back. Soon Jon and I will release an updated NerdDinner v2 on CodePlex (although it's been in the source code tab for weeks) with lots of fixes, new features. We'll also add many of these "one off" samples as well and host them on CodePlex.
I spoke to Microsoft Engineer Hamilton Verissimo de Oliveira, aka "Hammett" (you likely know him from the Castle Project and Monorail) about making a NerdDinner sample that included MEF (Managed Extensibility Framework) since much of MEF is built into .NET 4 now. He was kind enough to do it, but I'm just blogging it now, so thanks to Hammett for his kindness and patience.
NerdDinner on MEF
MEF lives in System.ComponentModel.Composition. Hammett's done a number of interesting things it his sample, adding Microsoft.ComponentModel.Composition.Extensions and Microsoft.ComponentModel.Composition.Extensions.Web namespaces building in some nice extension methods for common techniques as well as and implementation of IControllerFactory and a derivation of HttpApplication.
MefControllerFactory
Remember MEF is about making applications easily composable. In this sample Hammett has created his own MefControllerFactory, replacing the default controller factory that comes with ASP.NET MVC. ASP.NET MVC makes it easy to change:
protected override void Application_Start()
{
base.Application_Start();
ControllerBuilder.Current.SetControllerFactory(new MefControllerFactory(base.ScopeManager));
RegisterRoutes(RouteTable.Routes);
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new MobileCapableWebFormViewEngine());
}
Notice his controller factory has a ScopeManager. This is a web application, and some components might be at Application Scope (create them once and hang on) and some might be at Request scope (make a new one each request).
For controllers, he's effectively recreated the default behavior of the ASP.NET MVC's controller factory, but in doing it, has given us lots of ways we can jump in an change the behavior in exotic ways by overriding CreateRootCatalog in MefhttpApplication. It's default implementation looks in /bin:
protected virtual ComposablePartCatalog CreateRootCatalog()
{
return new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin"));
}
As you probably know, ASP.NET MVC looks for classes via a convention. It looks for classes with the word "Controller" at the end that are also IController implmentations. Here's the MEF way to declare that convention using Microsoft.ComponentModel.Composition.Extensions. Note the use of scope.
[assembly: Discovery(typeof(Conventions))]
namespace NerdDinner
{
public class Conventions : ConventionDiscovery
{
public Conventions()
{
Add(PartRegistration.
Types(t => t.Name.EndsWith("Controller") && !t.IsAbstract).
Exporting((c, t) => c.
Contract(typeof (IController)).
Metadata("Name", t.Name.Substring(0, t.Name.Length - "controller".Length)).
Metadata("Mode", WebScopeMode.Request))
);
}
}
}
Pretty cool.
Controllers and their Needs
Whenever a more advanced programmer looks as the NerdDinner code they usually say they they really don't like this:
public DinnersController() : this(new DinnerRepository())
{
}
public DinnersController(IDinnerRepository repository)
{
dinnerRepository = repository;
}
The second constructor takes an IDinnerRepository, allowing us to make different implementations, but the default constructor says, "well, here's a concrete implementation if you don't give one." It's a slippery slope and by adding the default implementation I get to sidestep using dependency injection while making the controller testable, but I've tied my controller down with a direct dependency to the DinnerRepository. This is sometimes called "Poor Man's IoC" and many would say that this is a very poor man. That's a religious argument, but Hammett takes a stand by removing the default constructor.
public class DinnersController : Controller
{
private IDinnerRepository dinnerRepository;
public DinnersController(IDinnerRepository repository)
{
dinnerRepository = repository;
}
//...
}
So how does a DinnersController ever get an IDinnerRepository? The idea is that it's not the controllers job to worry about the how, it's only its job to want.
MEF is effectively a Dating Service for Components. Here DinnerRepository is saying it's available and it wants to meet someone who is also into "IDinnerRepository."
[Export(typeof(IDinnerRepository))]
public class DinnerRepository : NerdDinner.Models.IDinnerRepository {
That [Export] attribute is its way to saying, "I'm on the market. Matchmaker, make a match!" When MEF is asked for a Controller and it notices that it has no default constructor, as in our case, it looks at the available constructors and says, "Oh, DinnersController wants to meet someone too! I I think I know just your type." Then it creates a DinnerRepository and calls the DinnersController constructor passing it in. It injects the dependency.
Often you'll see the other components advertising their interest with an [Import] attribute, but that's not necessary in this example because all the Controllers were created via the MefControllerFactory. They don't need attributes, as they've already walked in the door of our dating service!
Other Services MEFified
Recently in a review of MVC Music Store Ayende (and others before him, as well) dissed on these line of code, which actually come with ASP.NET MVC 2 out of the box and weren't written for the sample. (Although they weren't changed) Phil can speak to specific decisions as I wasn't involved, but many folks who are into dependency injection don't like this. This is effectively the same maneuver as shown above, written slightly differently.
public class AccountController : Controller
{
public AccountController()
: this(null, null) {
}
public AccountController(IFormsAuthentication formsAuth, IMembershipService service)
{
FormsAuth = formsAuth ?? new FormsAuthenticationService();
MembershipService = service ?? new AccountMembershipService();
}
//...
}
Hammett's implementation just uses MEF, so:
public AccountController(IFormsAuthentication formsAuth, IMembershipService service)
{
FormsAuth = formsAuth;
MembershipService = service;
}
Again, the whole point is that the dependencies get figured out automatically, each one wearing the "I'm available for hooks ups" attributes of [Export(typeof(IFormsAuthentication))] and [Export(typeof(IMembershipService))] respectively.
All in all, MEF is a nice clean addition to an ASP.NET MVC app. Thanks to Hammett for his hard work!
Related Links
About Scott
Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.
About Newsletter
Thank you for the blog post Scott.
Is there a registry class (ala StructureMap) to create application wide relationships?
Rich
Are you intentionally sending a signal to ayende not to code review NerdDinner?
Inferis, can't you override this and put your plugins where ever?
protected virtual ComposablePartCatalog CreateRootCatalog()
{
return new DirectoryCatalog(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "bin"));
}
Will you blog about the bigger advantage that MEF could bring? Like a **really** composable Application where dropping a dll into a plugin folder registered by MEF would add a new menu to the Web App for example?
The easy solution is putting it all in the standard folders, but that gets messy. I'd like xcopy plugin deployment where I just unpack a zipfile containing a plugin into a known folder and that's it. As far as I found out: that's hard to get right.
For those of us who are not C# programmers, could you provde us wth some Vsual Basic examples for the MobileCapableWebFormViewEngine code that you use to access the MDBF file
Thanks in advance
Scott
Comments are closed.
:-)