Asp.net MVC Areas in depth - Part 2
One of the first gotchas that will come across while using the asp.net MVC areas is in the usage of same controller name across different areas and the main project. For example the default asp.net MVC template has a HomeController and if we add the HomeController to one of the areas, accessing the home part of the areas will work fine but accessing the main home url will result in the following error
The controller name 'home' is ambiguous between the following types:
AreasInDepth.Controllers.HomeController
AreasInDepth.Areas.Blog.Controllers.HomeController
The easiest solution is to come up with unique controller names. Since the routing for the controllers in the areas works fine in this case and the only thing we have to fix is the main controllers we can use another concept to make this code work. The following code in Global.asax file is all that is required to fix this mess
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ControllerBuilder.Current.DefaultNamespaces.Add("AreasInDepth.Controllers"); 1
}
Simply, the above code instructs the DefaultControllerFactory to give priority for the controller inside the main project over those in areas and do not throw the previous InvalidOperationException if the controller name is happened to be unique among these default name spaces 1.
It's time to continue exploring the areas in asp.net MVC 2 preview 2
public Route MapRoute(string name, string url, object defaults, object constraints, string[] namespaces)
{
if (namespaces == null && Namespaces != null) {
namespaces = Namespaces.ToArray();
}
Route route = Routes.MapRoute(name, url, defaults, constraints, namespaces); 1
route.DataTokens["area"] = AreaName;2
bool useNamespaceFallback = (namespaces == null || namespaces.Length == 0);
route.DataTokens["UseNamespaceFallback"] = useNamespaceFallback; 3
return route;
}
MapRoute function of the AreaRegistrationContext class, discussed in the previous post, creates and adds 1 a Route to the global static RouteTable.Routes. It also places two data tokens namely area 2 and UseNamespaceFallback 3 which are later utilized in the route mapping workflow. If namespaces are not provided UseNameSpaceFallBack data token will have the value of true and the DefaultControllerFactory will probe the rest of the namespaces to get a valid controller type. This is a useful hook to prevent the DefaultControllerFactory from searching for controller in unintended areas. Rest of the magic happens during the request processing and we have to look the code inside DefaultControllerFactory, and here it is
protected internal virtual Type GetControllerType(RequestContext requestContext, string controllerName) {
if (String.IsNullOrEmpty(controllerName)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "controllerName");
}
object routeNamespacesObj;
Type match;
1
if (requestContext != null && requestContext.RouteData.DataTokens.TryGetValue("Namespaces", out routeNamespacesObj))
{
IEnumerable routeNamespaces = routeNamespacesObj as IEnumerable;
if (routeNamespaces != null) {
HashSet nsHash = new HashSet(routeNamespaces, StringComparer.OrdinalIgnoreCase);
match = GetControllerTypeWithinNamespaces(controllerName, nsHash);
if (match != null || false.Equals(requestContext.RouteData.DataTokens["UseNamespaceFallback"])) {
return match; 2
}
}
}
HashSet nsDefaults = new HashSet(ControllerBuilder.DefaultNamespaces, StringComparer.OrdinalIgnoreCase);
match = GetControllerTypeWithinNamespaces(controllerName, nsDefaults); 3
if (match != null) {
return match;
}
return GetControllerTypeWithinNamespaces(controllerName, null /* namespaces */); 4
}
GetControllerType, first searches 1 the route namesapaces for the matching controller type. It will exit the search if a match is found or the namespace fallback option 2 is set to false. If the previous two conditions were not satisfied the function will search for the type in the application's default namespaces collection 3. This is the scenario explained at the beginning of this post. Finally there will be a fallback option to search all of the namespaces for valid controller type. 4
3 Comments
Psyllos said
November 25, 2009
You're right, it solves issue of same controller name across different areas and the main project. But how can you deal with the view ? although the routing is defined for the controller into the area, the view displayed will be the one of the main project, no ?
german said
March 15, 2010
Hi!
Very useful info!! "UseNamespaceFallback" can be set to false on default route (not on areas registration but in the general MapRoute context) so that areas controllers are no used by the site's default route.
Thanks!!!
Germán. Spain.
Alexandre Brisebois said
May 26, 2010
Thankyou for saving me a HUGE amount of time !