UPDATEThis post is now obsolete. Single project areas are a core part of ASP.NET MVC 2.

Preview 1 of ASP.NET MVC 2 introduces the concept of Areas. Areas provide a means of dividing a large web application into multiple projects, each of which can be developed in relative isolation. The goal of this feature is to help manage complexity when developing a large site by factoring the site into multiple projects, which get combined back into the main site before deployment. Despite the multiple projects, it’s all logically one web application.

One piece of feedback I’ve already heard from several people is that they don’t want to manage multiple projects and simply want areas within  single project as a means of organizing controllers and views much like I had it in my prototype for ASP.NET MVC 1.0.

areas-folder-structure

Well the bad news is that the areas layout I had in that prototype doesn’t work right out of the box. The good news is that it is very easy to enable that scenario. All of the components necessary are in the box, we just need to tweak the installation slightly.

We’ve added a few area specific properties to VirtualPathProviderViewEngine, the base class for our WebFormViewEngine and others. Properties such as AreaViewLocationFormats allow specifying an array of format strings used by the view engines to locate a view. The default format strings for areas doesn’t match the structure that I used before, but it’s not hard for us to tweak things a bit so it does.

The approach I took was to simply create a new view engine that had the area view location formats that I cared about and inserted it first into the view engines collection.

public class SingleProjectAreasViewEngine : WebFormViewEngine {
    public SingleProjectAreasViewEngine() : this(
        new[] {
            "~/Areas/{2}/Views/{1}/{0}.aspx",
            "~/Areas/{2}/Views/{1}/{0}.ascx",
            "~/Areas/{2}/Shared/{0}.aspx",
            "~/Areas/{2}/Shared/{0}.ascx"
        },
        null,
        new[] {
            "~/Areas/{2}/Views/{1}/{0}.master",
            "~/Areas/{2}/Views/Shared/{0}.master",
        }
        ) {
    }

    public SingleProjectAreasViewEngine(
            IEnumerable<string> areaViewLocationFormats, 
            IEnumerable<string> areaPartialViewLocationFormats, 
            IEnumerable<string> areaMasterLocationFormats) : base() {
        this.AreaViewLocationFormats = areaViewLocationFormats.ToArray();
        this.AreaPartialViewLocationFormats = (areaPartialViewLocationFormats ?? 
            areaViewLocationFormats).ToArray();
        this.AreaMasterLocationFormats = areaMasterLocationFormats.ToArray();
    }
}

The constructor of this view engine simply specifies different format strings. Here’s a case where I wish the Framework had a String.Format method that efficiently worked with named formats.

This sample is made slightly more complicated by the fact that I have another constructor that accepts all these formats. That makes it possible to change the formats when registering the view engine if you so choose.

In my web.config file, I then registered this view engine like so:

protected void Application_Start() {
    RegisterRoutes(RouteTable.Routes);
    ViewEngines.Engines.Insert(0, new SingleProjectAreasViewEngine());
}

Note that I’m inserting it first so it takes precedence. I could have cleared the collection and added this as the only one, but I wanted the existing areas format for multi-project solutions to continue to work just in case. It’s really your call.

Now I can register my area routes using a new MapAreaRoute extension method.

public static void RegisterRoutes(RouteCollection routes) {
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

    routes.MapAreaRoute("Blogs", "blogs_area", 
        "blog/{controller}/{action}/{id}", 
        new { controller = "Home", action = "Index", id = "" }, 
        new string[] { "SingleProjectAreas.Areas.Blogs.Controllers" });
    
    routes.MapAreaRoute("Forums", 
        "forums_area", 
        "forum/{controller}/{action}/{id}", 
        new { controller = "Home", action = "Index", id = "" }, 
        new string[] { "SingleProjectAreas.Areas.Forums.Controllers" });
    
    routes.MapAreaRoute("Main", "default_route", 
        "{controller}/{action}/{id}", 
        new { controller = "Home", action = "Index", id = "" }, 
        new string[] { "SingleProjectAreas.Controllers" });
}

And I’m good to go. Notice that I no longer have a default route. Instead, I mapped an area named “Main” to serve as the “main” project. The Route URL pattern there is what you’d typically see in the default template.

If you prefer this approach or would like to see both approaches supported, let me know. We are looking at having the single project approach supported out of the box as a possibility for Preview 2.

If you want to see this in action, download the following sample.