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

17 thoughts on “Multi-Project (Portable) Areas Walkthrough

    • I can think of a couple of ways to do this:
      Convention: just reference the stylesheet in the markup, assuming the host application will provide it.
      Configuration Service: use a “shared kernel” to define a service interface that can provide the path. Mock/simple implementation in the development host project (“ParentProject” in the example).
      Message Bus: I didn’t touch on it, but MVCcontrib includes a very simple message bus to allow the portable area and the host application communicate without directly coupling them. Again, a message defined in a “shared kernel” assembly would be part of this solution.

  1. Hi,

    Thanks for the great explanation. I wonder, did you or anyone else got this working using MVC3? I keep getting the

    The view ‘index’ or its master was not found or no view engine supports the searched locations. The following locations were searched:

    exception.

    Thanks for your response, it is much appreciated!

    • Dennis,
      I haven’t tried this with MVC3 yet.

      One cause of that error is the index markup files not being embedded into the portable area assembly. If you are using a view engine other than the Web Forms view engine (something besides .aspx views), make sure you register the view enigine during “app_start.”

      Hope this helps

      • Hi Patrick,

        Thanks for your quick response! In the mean while I got it up and running. I installed the MVCContrib source so I could step through it and discovered that it wouldn’t match up the view with its virtual layout, I fixed it (the one you are using is correct obviously, but I copied mine from an existing project).

        I took it one step further by now, I load the portable area as an non-referenced plugin using MEF and NInject. I did this by making a custom ControllerFactory which falls back to a container when it can’t find the type from its DefaultControllerFactory. I can share this with you after I cleaned it up if you’d like, drop me an email ;-).

        Finest regards,
        Dennis Smit

        PS. Your blog post is super, helped me a lot to get started.

      • Ah, that makes sense… the folder structure in the portable area is crucial.

        Glad the post helped… good luck!

    • Did you ever find a solution to this problem? Having the same problem trying to access a layout page within the portable area. (Using Razor view engine)

      @pjboudrx, Great post, this really solved a big architectural problem we’ve been having (running asp.net mvc with multiple subdomains trying to map them into a big project turned out to be a big pain in the …)

      • Henrik,
        It seems the trouble Dennis was having was due to folder structure: views in the portable area MUST be under the path “~\(ProjectRoot)\(AreaNamespace)\Views”, and they either need to be embedded resources, or copied into the host application.

        Also, with a 3rd-party view engine (I’ve used Spark with this, but not Razor yet), you may need to establish an adapter to load your views from the embedded resource as opposed to disk.

        HTH,
        Patrick

      • I managed to solve the problem after downloading the MvcContrib source and walking through the debug a couple of times. Using a layout-page within the portable area (for a master-layout to all other parts) we needed to add
        “/Areas/” before the call to “/PortableArea/Views/Shared/MasterLayout.cshtml” because MvcContrib (or MVC itself) adds this to the resource path when trying to find files within the portable area.

        Thanks for the quick reply!

      • I was getting this exception:

        The layout page ~/Views/Shared/_Layout.cshtml could not be found …

        and resolved it by making a small change to _ViewStart.cshtml:

        Layout = “~/Areas/xxxx/Views/Shared/_Layout.cshtml”;

        where “xxxx” is equal to the AreaName property used in the PortableAreaRegistration

    • Gayani,
      Absolutely, as Henrik said.

      You build the controllers in the same way as you would in a non-portable area, or in a non-area application. In the example above, I would just add another controller class in the same namespace (and same folder) as the “HelloWorldController”

  2. Portable areas are great. I have few pluggable components now. I want to use the same in Older Webforms. The same url gives The view ‘Details’ or its master was not found or no view engine supports the searched locations(but they are there as it works in MVC app). However if I changed the action result to string works. What view engine reference is need to use in the web forms? I have both Razor and webpage reference in the portable area components

    • I’m not sure I understand your scenario: do you mean you want to use the WebForms view engine?

      I’m not familiar with that engine, but the key is registering the view engine at app_start – I think you can do this inside the portable area, perhaps from the RegisterPortableArea method.

  3. Pingback: MVC Area in an External Assembly | The Code Snob

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s