Inside Areas Registration (How does RegisterAllAreas work in ASP.NET MVC 4)
Recently I've faced the problem with registering the areas. I added new area to my application, but it didn't seem to work. In this article I'll explain how does the areas registration work under the hood and then how I solved my problem.
How to create areas?
Using Visual Studio it is pretty easy. Just select the project in Solution Explorer,
right click and select Add > Area...
. The folder structure is created
automatically.
Each area has its own class which inherits from AreaRegistration
class. The
purpose of this class is to register new routes (you add new controllers which
will be available using new addresses - the framework has to know about that).
In this model each area registers its own routes rather than all routes from all
areas are registered in one place.
public class Area1AreaRegistration : AreaRegistration
{
public override string AreaName
{
get { return "Area1"; }
}
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"Area1_default",
"Area1/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional });
}
}
But shouldn't there be some entry point where areas can plug themselves in? There
is. If you open Global.asax
you will notice that there is one line of
code being automatically added by Visual Studio.
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
...
}
}
How does RegisterAllAreas work?
Let's look in the source code of AreaRegistration
class. For this
we will need to use a decompiler. I recommend my favorite
dotPeek from JetBrains. It's
100% free and it has the same shortcuts as ReSharper.
The method we are interested in is static RegisterAllAreas
(there are three but actually only one does whole job).
internal static void RegisterAllAreas(
RouteCollection routes,
IBuildManager buildManager, object state)
{
foreach (Type type in TypeCacheUtil.GetFilteredTypesFromAssemblies(
"MVC-AreaRegistrationTypeCache.xml",
new Predicate<Type>(AreaRegistration.IsAreaRegistrationType),
buildManager))
((AreaRegistration)Activator.CreateInstance(type))
.CreateContextAndRegister(routes, state);
}
It looks like a work it does is pretty straightforward. First of all it uses
TypeCacheUtil
to find assemblies which contain classes that inherit
from AreaRegistration
class. The condition is checked in
AreaRegistration.IsAreaRegistrationType
(you can also notice that
the class should have parameterless constructor).
private static bool IsAreaRegistrationType(Type type)
{
if (typeof (AreaRegistration).IsAssignableFrom(type))
return type.GetConstructor(Type.EmptyTypes) != (ConstructorInfo) null;
else
return false;
}
What is interesting, the classes which are found are stored in
MVC-AreaRegistrationTypeCache.xml
file. The file is located at
c:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files\NAME_OF_YOUR_APP\
(source: MVC-ControllerTypeCache.xml in MVC).
The next step of the RegisterAllAreas
method is to create an instance
of each of these classes and call RegisterArea
on them. This is the
method that we overriden in our area and registered its routes.
Solving the problem
Let's go back to my initial problem. To remind you: the application wasn't
registering routes from my new area. The only suspicious place is the xml file
with cached AreaRegistration
classes. Caching is very useful when it
comes to performance tuning of an application, but in the same way it can bring
many problems. It turned out that my new AreaRegistration class wasn't listed
there. So I've deleted this file, restarted application and the problem was solved.