Multi-Project (Portable) Areas Walkthrough

With MVC2, the MVC team introduced Areas, a way to decompose and organize a large MVC application into smaller sub-applications.  The guys who work on MvcContrib built on top of this the concept of Portable Areas.

The Asp.NET site has an intro video on Areas here.

Eric Hexter gives a good overview of portable areas here, and he talks about it with Jeffrey Palermo on the Polymorphic Podcast here.

Eric and Jeffery wrote pretty thorough walkthroughs of portable areas, but they’ve since been outdated by some API changes in MvcContrib.  When a teammate and I attempted to create and consume our own portable area from scratch, we were thwarted by this, and the fact the the above examples also walk through other, unrelated MVC/MvcContrib features.  It took us about a day-and-a-half of trial-and-error to get our own from-scratch portable area, and the necessary steps turned out to be relatively simple, so I thought I’d capture those here.

Note: The MvcContrib project has a Portable Areas Sample in the codebase, at http://mvccontrib.codeplex.com/.  As of this post, the sample works perfectly, but we needed to create our own, from scratch for a new project.  There also appears to be a portable area project template in the works, too, which would be helpful.

Caveat:

I used MVCContrib 2.0.47.0(unreleased) to build this example.  There were some API changes from the latest MvcContrib release (2.0.36.0, 4/14/2010).  I pushed the result to http://github.com/pjboudrx/PortableAreasWalkthrough

Overview/Checklist

  1. Build The Area (Models, Views & Controllers)
  2. Portable Area Registration
  3. Embedded Views and Static Content
  4. “Areas” folder with a Web.Config

2 Projects

To develop the portable area, we found it best to have a related ‘host’ MVC project in the solution, in addition to the portable area project itself.  This gives us an environment to test our portable area.

‘Parent’ Project: MVC2 Web Application

Add a reference to MVCContrib.

‘Portable’ Project: Class Library

Add references to MVCContrib, System.Web.Mvc and System.Web.Routing

Build the portable area

This is equivalent to taking a single area and moving under the root of the project.  You have a folder with the area name.  In that folder: Controllers, Models, Views folders.  For example, my portable area is named ‘CoolComponent’:

Portable Area Registration

The portable area registration class must reside in the namespace <RootNamespace>.<AreaName>; for example: “PortableArea.CoolComponent” Example

namespace PortableArea.CoolComponent
{
	public class CoolComponentRegistration : PortableAreaRegistration
	{
		public override string AreaName
		{
			get { return "CoolComponent"; }
		}

		public override void RegisterArea(AreaRegistrationContext context, IApplicationBus bus)
		{
			base.RegisterArea(context, bus);
			context.MapRoute(
 				"CoolComponent_Default",
 				AreaName + "/{controller}/{action}/{id}",
 				new {controller = "HelloWorld", action = "Index", id = UrlParameter.Optional });
 		}
 	}
 }

What’s going on here?  First, the simple stuff.  The “AreaName” property just fills in a spot in the template pattern established by PortableAreaRegistration for use elsewhere.  The call to “context.MapRoute()” is the same as a call to “routes.MapRoute()”; only this time, the AreaName is fixed in the URL pattern.  Standard MVC Area stuff.

The line with all the juice is the call to “base.RegisterArea().”  The base method does several things for us:

  1. Sends a “PortableAreaStartupMessage” on the Application Bus
  2. Registers routes and a controller for embedded Images, Styles, and Scripts, to make the Area fully portable
  3. Registers the Embedded View Engine, so your embedded .aspx view templates can be found at runtime

Add controller(s) and view(s)

Because your portable area is just a class library, you may not see VS templates like you’d expect.
Note: views must be embedded resources.

Consume the Portable Area

Make sure you have an “Areas” folder in the Parent project, and copy Web.config from “Views” to “Areas” if it does not already exist.


Add a reference to the Portable Area project
Global.asax.cs: Add calls to ‘RegisterAllAreas’ and ‘RegisterEmbeddedViewEngine’ (Example)

protected void Application_Start()
{
	// This registers areas, both in-project and portable
	AreaRegistration.RegisterAllAreas();

	// This ensures the views embedded in the Portable Area can be found
	PortableAreaRegistration.RegisterEmbeddedViewEngine();

	// Typical MVC route registration call
	RegisterRoutes(RouteTable.Routes);
}

Test it!

Browse to /CoolComponent/HelloWorld/Index

Area-aware links in the Parent Project

If you want to link (or RenderAction) to the portable areas routes, you will need to ensure ‘area’ is in the route data.

Example

<!-- Ensure 'area' is in the route data  -->
<%=Html.ActionLink("Use the Cool Component", "Index", "HelloWorld", new {area="CoolComponent"}, null) %>
<!-- Ensure 'area' is in the route data  -->    <%=Html.ActionLink("Use the Cool Component", "Index", "HelloWorld", new {area="CoolComponent"}, null) %>

Portable Images, Scripts and Styles

(Thanks to Steve Harman for this additional insight.)

To make a fully self-contained portable area, static content such as css, images and javascript needs to be embedded in the assembly, and found in a special way, just as the view templates are.  To do this:

  1. be sure to call “base.RegisterArea()” in your Portable Area Registration
  2. Include the static files as embedded resource in the conventional location:

Advertisements

Thoughts on C# Generic Constraints

A teammate and I were musing about generic constraints the other day, comparing them to checked exceptions in Java.  So I found it amusing that Jeremy Miller came to the same point with Scott Allen yesterday:

Question of the Day — What’s Worse?generic constraints, or checked exceptions [in java]?

Wouldn’t it be nice if we could “break the chain” of constraints at some point, when the calling code ceases to care?  Since I don’t know of a pattern for that, is there something we can do to minimize the constraint pain?

What about the case where the constraint is there only for the convenience of the implementation; say, being able to call “new T()”?

For example:

T Find<T>(int id) where T : Entity, new()
{
  var result = new T();
  ...// something to do with the Entity type
  return result;
}

So now, we have this “new()” constraint all over.  Yuck!  Is “Activator.CreateInstance()” really that hard to type?  I understand that “new T()” reads a lot nicer… but that’s one line of code vs. however many consumers now muddied.

Of course, you might say the “Entity” constraint is just as arbitrary to the calling code (more so as you go up the stack).  However, that constraint helps us do something meaningful, but we can live without the “new(),” which provides no real value.

So considering we’re stuck with the current compiler behavior for now, I’d suggest using constraints more to add value to the interface, and less for shortcuts in the one instance of code.  The downside, in the case of “new()”, is that we won’t know about a missing default constructor until run time, but that should be during the unit tests, which run on every build, right?