Site Meter
 
 

App Areas in ASP.NET MVC, take 2

So the discussion continues: How do you partition an ASP.NET MVC application into separate “areas” or “modules” (e.g., blog module, e-commerce module, forums module), then compose a finished app from those areas or modules?

Phil Haack, ASP.NET MVC program manager, just posted a prototype application structured in that way. It’s a really neat solution, and overcomes a number of issues that others, including myself, have experienced when trying to do it previously.

In this post, I’m going to take Phil’s prototype and tweak it in a few ways to my liking. That’s not to say that there’s anything wrong with his design, but only that I want to throw in some extra ideas that might make it even slicker in some cases.

How it works

The mechanism as given comes in two parts:

  • Routing configuration: There’s an extension method on RouteCollection called MapAreas() which lets you register a URL pattern for multiple areas. You pass it a URL pattern, a controller “root namespace”, and an array of area names. It prefixes “{area}/” to your URL pattern, and for each area name, it registers a route entry that targets controllers who live in the namespace given by: 
    (root namespace + “.Areas.” + area name + “.Controllers”)
  • View engine: There’s a special view engine called AreaViewEngine that uses a built-in convention to look for view templates in an area-specific folder.

Don’t worry if you don’t understand this. If you download Phil’s prototype and try it, you’ll find it’s all straightforward enough.

It works very nicely, and it uses a clever trick with route defaults and constraints so that when you’re generating outbound URLs, you can link to controllers/actions in any area by specifying the area name in your Html.RouteLink() call, or you can link to controllers/actions within the same area using a normal Html.RouteLink() call that doesn’t specify any area name.

Suggestions for enhancement

I’m not going to touch the view engine part of the prototype at all. All I want to achieve is a configuration system that’s slightly more natural (for me) and a modified set of conventions and rules that are a bit more flexible. Here’s how I’d like a simple areas routing configuration to look:

// Routing config for the blogs area
routes.CreateArea("blogs", "AreasDemo.Areas.Blogs.Controllers",
    routes.MapRoute(null, "blogs/{controller}/{action}", new { controller = "Home", action = "Index" })
);
 
// Routing config for the forums area
routes.CreateArea("forums", "AreasDemo.Areas.Forums.Controllers",
    routes.MapRoute(null, "forums/{controller}/{action}", new { controller = "Home", action = "Index" })
);
 
// Routing config for the root area
routes.CreateArea("root", "AreasDemo.Controllers",
    routes.MapRoute(null, "{controller}/{action}", new { controller = "Home", action = "Index" })
);

… and here’s an example of a very slightly more complex configuration:

// Routing config for the blogs area
routes.CreateArea("blogs", "AreasDemo.Areas.Blogs.Controllers",
    routes.MapRoute(null, "SpecialUrlForPosts", new { controller = "Home", action = "Posts" }),
    routes.MapRoute(null, "blg/{controller}/{action}/{id}", new { action = "Index", controller = "Home", id = "" })
);
 
// Routing config for the forums area
routes.CreateArea("forums", "AreasDemo.Areas.Forums.Controllers",
    routes.MapRoute(null, "myforums/SecretAdminZone/{action}", new { controller = "Admin", action = "Index" }),
 
    // Equally possible to construct routes using "new Route()" syntax too
    new Route("myforums/{controller}/{action}", new MvcRouteHandler()) {
        Defaults = new RouteValueDictionary(new { controller = "Home", action = "Index" })
    }
);
 
// Routing config for the root area
routes.CreateArea("root", "AreasDemo.Controllers",
    routes.MapRoute(null, "{controller}/{action}", new { controller = "Home", action = "Index" })
);

The point to notice is that each area is configured independently of the others, which for me feels more natural than defining a single URL pattern that for some reason applies to all areas.

It turns out to be dead easy to make it work like this. All you need is this simple CreateArea() extension method:

public static void CreateArea(this RouteCollection routes, string areaName, string controllersNamespace, params Route[] routeEntries)
{
    foreach (var route in routeEntries)
    {
        if (route.Constraints == null) route.Constraints = new RouteValueDictionary();
        if (route.Defaults == null) route.Defaults = new RouteValueDictionary();
        if (route.DataTokens == null) route.DataTokens = new RouteValueDictionary();
 
        route.Constraints.Add("area", areaName);
        route.Defaults.Add("area", areaName);
        route.DataTokens.Add("namespaces", new string[] { controllersNamespace });
 
        if (!routes.Contains(route)) // To support "new Route()" in addition to "routes.MapRoute()"
            routes.Add(route);
    }
}

Apart from that, there’s no difference from the original prototype (well, you can delete the original prototype’s MapAreas() and MapRootArea() methods: they’re no longer used).

Benefits

How does this differ from the original prototype? What conventions have changed?

  • URL patterns are no longer forced to start with the area name (though they can if you want). Within a single area, you can have some URLs that start with the area name, and some that don’t.
  • In fact, URL patterns are now totally independent of area names, which means your area names can simply be internal code words for software modules, never seen by the public. If that’s what you want.
  • Controller namespaces are independent of area names, and they don’t even have to be constant within a single area. Pick your own convention and follow it.
  • You can  configure each area’s routes in a separate block of code, using a sweet DRY syntax, which for me feels more natural than having a single method call that registers routes across all areas.
  • You don’t need any special code or configuration for the “root” area – that’s just another area like the others, usually just with shorter URL patterns.
  • You can keep using the familiar routes.MapRoute() and new Route() ways of building route entries, merely wrapping up groups of them in CreateArea() calls. No significant new API.

Apart from this, it uses almost exactly the same mechanism as in the original prototype.

Drawbacks

If enforcing conventions is your thing, then you might not appreciate the extra flexibility that comes with these changes.

Also, when you do cross-area links, you have to be careful to specify a controller name and not just assume the default controller will be used. Otherwise, the generated URL might reuse the current request’s controller name, which might not even exist in the destination area. This is because of an obscure technicality in how URL generation works (it doesn’t affect Phil’s original design because of how he requires all URLs to start with an “{area}” segment).

This isn’t worth explaining in detail – all I’ll say is that the solution is simply to make sure your cross-area links always specify a controller name. Of course, you almost certainly should be doing that anyway, because it would be very weird to link just to an action name on a different area without being clear about which controller hosts that action.

Summary

ASP.NET MVC continues to impress me with its flexibility. If you want to structure your app in terms of “modules” or “areas”, it doesn’t take much code to enable it. Phil’s approach to areas is the neatest I’ve seen so far. Personally I like the tweaks I’ve suggested above, but it’s subjective and you can do things your own way.

40 Responses to App Areas in ASP.NET MVC, take 2

  1. Another great post, really looking forward to the book coming out because if it’s anything like these blog entries it’ll be an invaluable MVC resource.

  2. I like them as well, and it’s easy enough to have versions of MapArea that are more convention based that wrap the versions you wrote.

    For my prototype, I went a bit overboard with the conventions because it made developing the prototype faster. As I take it further, I’ll probably dial it back a bit to something more manageable like what you did. Nice work!

  3. Maciej Nejmantowicz

    Great post. Just what I was looking for. Can you please post the sample code?

  4. Eric

    Are things like this going to make it into your upcoming book are are they too last minute?

  5. Pingback: Nov 6th Links: ASP.NET, ASP.NET AJAX, jQuery, ASP.NET MVC, Silverlight and WPF - ScottGu's Blog

  6. Steve

    @Maciej – to get this working, download Phil Haack’s prototype (linked from http://haacked.com/archive/2008/11/04/areas-in-aspnetmvc.aspx), then copy & paste this blog post’s CreateArea() extension method listing into it. Then you can change the routing configuration to look like one of the listings I posted above.

    @Eric – it’s not completely certain what will be in my upcoming book when it’s finished, because that will depend on how the MVC framework itself looks at RTM. The book takes developers from a low knowledge of ASP.NET MVC to a thorough and in some places advanced knowledge, focusing on how the framework is intended to be best used, whereas my blog tends to emphasise more experimental techniques such as “app areas” and “partial requests”.

  7. Pingback: Dew Drop - November 6, 2008 | Alvin Ashcraft's Morning Dew

  8. Eric

    Thanks Steve.

    Hope you include enough Advanced stuff in the book!

  9. crocod

    Thanks for this post – it makes Phil’s idea more practical & natural.
    I would like to use this approach to my next application just coming.

  10. Eric

    Thanks! This works great. Now I have one question about the web.config file. Each one of our “areas” needs to have specific information that would not be known at the top level. i have added a web.config file to each area but cannot seem to get the appsettings section to load into the Configuration object.

  11. Steve

    @Eric – you might like to create a separate web.config file holding settings for each area, then reference all those files in your top-level web.config. For an example, see http://www.devx.com/vb2themax/Tip/18880 . Note that you can use the “file” attribute to import arbitrary config nodes, not just appSettings.

  12. Pingback: Areas in ASP.NET at { null != Steve }

  13. Rich

    What can you see as being the cleanest method of preventing the routing mechanism ‘falling through’ to the root controllers, etc?

    For example, if you have a mobile and main website, some sections will be deliberately left out of the cut-down mobile site.

    It would be useful to be able to return a 404 if one of these is requested, instead of falling through.

  14. Steve

    @Rich – You might want to put all your “mobile” routes at the top of the config, followed by an entry that catches all mobile requests and returns a 404, followed by all the regular route entries. Exactly how to do that depends on how you are distinguishing “mobile” URLs or requests from regular ones.

  15. Hi,

    I am using asp.net mvc preview 5 for one of my project. If i run my website from IIS 5 then it throws below error. But if i run using visual studio 2008 F5 then it works fine. If anyone faced this kind of problem please let me know how you fixed it

    Regards,
    Saravanan

  16. Rich

    @Steve: thanks for the rapid reply – I’ll give it a go.

  17. Mathew

    This has been extremely helpful, looking forward to the book, just running into one problem…

    On XP Pro, VS 2008 SP1 and MVC Beta.

    Put “Manager” area in own folder and can access the controllers with full Url, however it’s not hitting the default controller.

    // Routing config for the manager area
    routes.CreateArea(“Manager”, “AreasDemo.Manage.Controllers”,
    routes.MapRoute(null, “manage/{controller}/{action}”,
    new { controller = “Category”, action = “List” })
    );

    http://localhost:2088/home WORKS
    http://localhost:2088/manage/category WORKS
    http://localhost:2088/manage/category/list WORKS

    BUT…

    http://localhost:2088/manage FAILS (HTTP 404)

  18. Steve

    @Matthew – there must be some issue to do with your routing config, but I can’t tell what it is without seeing more. If you can email me a minimal runnable ASP.NET MVC project that demonstrates the issue then I’ll have a look.

  19. Pingback: MVC "Areas" as Hierarchical Subfolders under Views - Billy McCafferty

  20. Andrew Cameron

    This has been extremely useful, thanks! I was trying to get Phil’s solution to work with the route “/site/{*id}” but kept getting errors when it tried to find the relevant view. After creating the area using your method, it worked instantly! Thanks again.

  21. Steve

    @Andrew – Great! Glad to be of help.

  22. Pingback: 15、ASP.NET MVC Beta 发布了 - Sample Weblog - IC窗口

  23. Mike

    Using it, loving it!

    Thanks so much for doing this, it really solved some issues I had with multiple projects nested in IIS… I don’t even want to go there anymore now that I have areas it is much more simple.

  24. JC

    I’m having the same problem as Matthew. If I explicitly go to a URL within an area then everything is fine, but it never seems to find the default route for an area.

    This works great:
    http://www.domain.com/Client/Home/Index

    This gives a 404 error:
    http://www.domain.com/Client

    Any ideas?

  25. Pingback: You should NOT use ASP.NET MVC if. . . : Jeffrey Palermo (.com)

  26. Pingback: The S#arp framework: wrapping up the SharpArch.Web assembly study - LA.NET [EN]

  27. I ran into a problem with using AutoFac and this code where controllers had to have unique names. I figured out how to extend the area capability into the ControllerFactory by implementing an identification strategy and subclassing the factory. Check it out at http://blogs.thirdfamily.com/michael/?p=37

  28. Pingback: Areas in ASP.NET MVC « Memory Dump

  29. Pingback: Martijn Boland » ASP.NET WebForms and MVC together in one project

  30. What about canonical URL’s generated by a SiteMap and parameters passed to a URL using app areas? For example, check out the canonical URL section in http://www.asp.net/learn/mvc/tutorial-20-cs.aspx. It only defines a solution per URL using MapRoot. So generating links via a SiteMap isn’t do-able the minute you pass parameters to the URL as it hasn’t got any URL to match up with.

  31. Steve

    @Kezzer – I’m not sure what problem you’d experience. Are you suggesting that you’re generating your routing config from the sitemap, and therefore can’t accept arbitrary querystring parameters in URLs?

    If that’s the case, you might want to check out Chapter 15 of my ASP.NET MVC book which describes a way of working with SiteMaps that’s fully compatible with ASP.NET MVC-style routing with arbitrary parameters.

    If I’ve misunderstood your point, please let me know!

  32. Rich Urwin

    @Steve,

    I’m using these app areas and they’re great. I’m now trying to introduce handling of the odd legacy url, using what seems to be the most widely used code by Matt Hawley:

    http://blog.eworldui.net/post/2008/04/ASPNET-MVC—Legacy-Url-Routing.aspx

    the trouble is, I can’t get it to work – I’ve tried adding my LegacyRoutes both inside and outside the routes.CreateArea in Global.asax.cs but no joy.

    Are you familiar with this code and if so, any idea how to get it to work with your app areas? Might be a good blog post!

    Cheers,
    Rich

  33. Adeel

    Phil’s blog http://haacked.com/archive/2009/07/31/single-project-areas.aspx

    is dated after this one and you guys are refining it constantly. I have been asked to decipher and introduce areas in our project and I’m a newbie in web development. I’m not sure which approach I must follow for introducing areas.

    Finally Steve, I bought your book published in 2009 and I didnt find any discussion on design by areas.

  34. Adeel

    This is for anyone who is new to areas. This solution is great when incorporated in Phil’s prototype. However, both failed to work for me and I think because I’m using MVC preview-2. I changed references in Phil’s protiotype to System.Web.Mvc 2.0.0.0 and it stoppped working.

    So please make sure what ver. of assembly you are working with.

  35. Niyi Ibironke

    Routing configuration is perhaps the most infuriating aspect I’ve been faced with since trying to use Areas.
    Everyone I’ve spoken to appears to have one code sample or the other but don’t really understand how to knit routing when faced with a combination of:
    - Areas
    - unlimited depth SEO friendly URLs (generated dynamic by a sitemap or other mechanism)
    - MVC paging.
    Is there a resource (book, sample, training, etc.) that addresses this?
    Help!!!

  36. It’s a difficult experience for almost everybody to get by and propser in this market. Just a number of internet marketers and internet businesses really generate income. So, I appreciate your content and I wish this will guide other individuals also to produce their unique way to accomplishment. As a marketer I am even so a believer in the email marketing formula and I am also using safelist in my home business as an tool to get new potential clients. I lately happened upon a new one, an incredible new conception, WPSafelist, you would likely want to check it.

  37. Tempie Kenekham

    ÿþ|

  38. Thank you for taking the time to put up this sample….so far it’s working great for me!!! What I had to do is start with a clean empty project and create one area at a time and baby step my way through it so I can truly understand what was happening regarding routing …when making my areas a little verbose for SEO I was getting tangled up but baby steps, THIS EXAMPLE, and bite size chunks was the key ….thank you again.

  39. I’ve been reading by means of the comments and it appears like they’re receiving targeted guidance

  40. Any careful understanding and concepts I am going to experience our site