In ASP.NET web forms I’ve used the “sub-web project” trick to break apart large web applications. It has some downsides, but generally works. ASP.NET MVC 2 will include built-in support for breaking apart a large MVC application into “areas”. To quote ScottGu:
Areas provide a means of grouping controllers and views to allow building subsections of a large application in relative isolation to other sections. Each area can be implemented as a separate ASP.NET MVC project which can then be referenced by the main application. This helps manage the complexity when building a large application and facilitates multiple teams working together on a single application together.
There is a detailed walkthrough on MSDN for creating an Areas application using multiple projects. You create a parent project (MvcAreasMultiProject) and two sub-projects (Accounts and Store).
Notice the “s” on the Accounts controller name – there is an Account controller and an Accounts controller in the application – we’ll come back to that.
Inside the parent project, use the AreaRegistration class to magically register all the routes in all child projects.
public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); AreaRegistration.RegisterAllAreas(); routes.MapRoute( "Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "" } ); }
The AreaRegistration class will scan assemblies looking for types derived from AreaRegistration. It will instantiate these types and execute a method to give the the child projects a change to register their own routes. For example, the Accounts sub-project registers it’s routes with the following code:
public class Routes : AreaRegistration { public override string AreaName { get { return "Accounts"; } } public override void RegisterArea( AreaRegistrationContext context) { context.MapRoute( "Accounts_Default", "Profile/{action}/{id}", new { controller = "Accounts", action = "Index", id = "" } ); } }
With routes in place you can now generate links that reach specific areas. The following snippet creates a link to the Accounts controller in the Accounts sub-project…
<%= Html.ActionLink("Accounts", "Index", "Accounts", new { area = "accounts" }, null)%>
… while this one links to the Account controller in the parent project…
<%= Html.ActionLink("Log On", "LogOn", "Account", new { area = "" }, null)%>
What happens if two of the projects have a controller with the same name? For instance, if both the parent project and the Accounts project have an AccountController (with no “s” on Account).
If you try to reach the AccountController inside the Accounts area - everything should work. This is because the AreaRegistrationContext used to register routes for the Accounts area is automatically adding a namespace value to restrict the controller search. It’s like using the following code:
namespace Accounts // ^^ // the namespace of the area registration type // is used by default when mapping routes { public class Routes : AreaRegistration { public override string AreaName { get { return "Accounts"; } } public override void RegisterArea(AreaRegistrationContext context) { context.MapRoute( "Accounts_Default", "Profile/{action}/{id}", new { controller = "Account", action = "Index", id = "" }, null, new string[] { "Accounts" } // ^ this is explicitly specifying // the namespace as "Accounts", // but "Accounts" is the // default, so this isn't needed ); } } }
Note that the AccountController doesn’t have to live in the root Accounts namespace. It can live further down the hierarchy (like Accounts.Controllers.AccountController) and the factory will still find it.
The child area is in good shape, but a problem can occur if you try to reach the AccountController in the parent project. If the routes for the parent project were not given a any namespace values (which they aren’t by default), then the default controller factory will become angry and throw an exception at runtime.
The controller name 'Account' is ambiguous between the following types:
MvcAreasMultiProject.Controllers.AccountController
Accounts.Controllers.AccountController
The easiest solution is to include the namespace(s) for your parent project controllers when registering routes in the parent area.
routes.MapRoute( "Default", "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "" }, null, // namespaces -> new string[] { "MvcAreasMultiProject" } );
Coming up next Tuesday, my answers to the following questions: