Get Areas associated with an MVC project
Asked Answered
W

3

6

Is there a way I can get the names of the areas in an MVC project?

Here's a few ways I can think of:

a) If I had the source, I could browse through the project folder structure and enumerate the folders under the Areas folder. But that wouldn't guarantee all the folders represent areas unless I also enumerated the Controllers and Views folder under each of the sub-folders. This approach, as you can tell, sucks.

b) From the binary, I could enumerate all namespaces that match the criteria RootNamespaceOfProject.Areas.*.

Or, I am sure there's a more elegant way. There must be some dictionary in the ASP.NET MVC framework that keeps a record of all the areas.

Secondly, is there also a programmatic construct in the MVC framework that represents an area? I can't seem to find one. There are only four constructs that are related to areas:

 1. AreaRegistration
 2. AreaRegistrationContext
 3. IRouteWithArea
 4. AreaHelpers (an internal class)

If there were one, would it be possible, say, to enumerate all the controllers within that area?

Edited

I just noticed that there's this file called MVC-AreaRegistrationTypeCache.xml in the folder \Windows\Microsoft.NET\Framework\v4.x.x\Temporary ASP.NET Files\root\RandomlyGeneratedHash1\RandomlyGeneratedHash2\UserCache.

This folder has two files:

a) MVC-AreaRegistrationTypeCache.xml: This file has the list of all the areas in all the assemblies on the machine that have areas.

b) MVC-ControllerTypeCache.xml: This file lists the controllers within the areas of the assemblies.

Now, the only thing to find out is if there's some programmatic way to have the MVC framework read these files and tell me if a certain area exists in a binary.

I am thinking that the AreaRegistration class might be the one. Exploring it further...

Woodall answered 1/9, 2010 at 15:56 Comment(1)
Both of these ideas are no-win solutions as they depend on something you have no way of controlling! Areas can be placed anywhere in the project structure so long as they are registered and mapped.Cervantez
C
6

It would seem that the only way you will be able to retrieve the routes registered in your project is by enumerating the project for types which inherit AreaRegistration, there does not appear to be any object private or public which tracks currently registered areas.

Long explanation follows...

One hurdle to keep in mind here is that areas are little more than a coupling between an arbitrary string and a list of namespaces. When an area is registered it is merely extending the route collection for the application with some new rules identifiable by a unique "area" DataToken.

If you look at the process for registering an area, you must inherit from System.Web.Mvc.AreaRegistration and override RegisterArea(). RegisterArea() receives an AreaRegistrationContext which defines an area name, route collection and object state, but if you observe the format for implementing RegisterArea(), it returns void and does nothing to preserve the context object. What's more, if you look at the code which runs before RegisterArea() is fired (Reflector), you can see that the AreaRegistrationContext object which is passed to RegisterArea() is never permanently tracked.

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);
    }
}

internal void CreateContextAndRegister(RouteCollection routes, object state)
{
    AreaRegistrationContext context = new AreaRegistrationContext(this.AreaName, routes, state);
    string str = base.GetType().Namespace;
    if (str != null)
    {
        context.Namespaces.Add(str + ".*");
    }
    this.RegisterArea(context);
}

As you can see, a call to the static method RegisterAllAreas() invokes the internal RegisterAllAreas(RouteCollection routes, IBuildManager buildManager, object state), which then calls the internal CreateContextAndRegister(RouteCollection routes, object state), which creates the AreaRegistrationContext and passes it to RegisterArea().

As far as I can tell, never, at any point, is the AreaRegistrationContext created for each area stored permanently.

Cervantez answered 1/9, 2010 at 16:36 Comment(2)
Spot on, Nathan! I noticed the same thing. Thanks much. So, I could get all types that are derived from AreaRegistration and then compare the AreaName property value of the object with the name of the area I am looking for. Great! The bad thing is, there's really no mapping between an area and its controllers that is preserved anywhere.Woodall
@Water Cooler You could parse the Routes.RouteCollection and check the "Namespaces" DataToken (string[]) of each route and compare those values to the namespaces of all of your registered controllers. I'm curious what the intended use of this functionality is as there may be a better option which hasn't been explored.Cervantez
C
1

You could do what MVC is doing, iterate all the referenced assemblies and look for classes that inherit from AreaRegistration. Then you can simply get the AreaRegistration.AreaName.

I'm planning on doing this to build my top-level navbar using Twitter Bootstrap.

Chura answered 6/3, 2012 at 8:50 Comment(3)
How do you iterate all the referenced assemblies?Starve
Something like new[] { Assembly.GetExecutingAssembly() }.Concat(Assembly.GetExecutingAssembly.GetReferencedAssemblies());Chura
You can use AppDomain.CurrentDomain.GetAssemblies() to get all I thinkMalaise
T
1

This thread was helpful in finding the answer, but nobody here posted it explicitly. Here is what I came up with, but do note you may need to adjust it depending on whether all of your areas are in the same assembly.

private IEnumerable<AreaRegistration> GetAllAreasRegistered()
{
    var assembly = this.GetType().Assembly;
    var areaTypes = assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(AreaRegistration)));
    var areas = new List<AreaRegistration>();
    foreach (var type in areaTypes)
    {
        areas.Add((AreaRegistration)Activator.CreateInstance(type));
    }
    return areas;
}
Ticon answered 15/11, 2013 at 22:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.