12

I noticed that in MVC 2 Preview 2, AreaRegistration is loading the routes for each area in an arbitrary order. Is there a good way to get one before the other?

For example, I have two areas - "Site" and "Admin". Both have a "Blog" controller.

I would like the following:

/admin/ --> go to Admin's Blog controller
/       --> go to Site's Blog controller. 

The problem is that it is loading the site's route first, so it is matching {controller}/{action}/{id} instead of admin/{controller}/{action}/{id} when I go to the url "/admin/". I then get a 404, because there is no Admin controller in the "Site" area.

Both areas default to the "Blog" controller. I realize I could simply put site/{controller}/... as the url, but I would rather have it at the root if possible. I also tried keeping the default route in the global RegisterRoutes function, however, it is then not sent to the "Sites" area.

Thanks in advance!

Jason
  • 2,521
  • 25
  • 28

4 Answers4

30

Aside from what Haacked said, it is very much possible to order area registrations (and thus their routes). All you have to do is register each area manually, in whatever order you want. It's not as sleek as calling RegisterAllAreas() but it's definitely doable.

protected void Application_Start() {
    var area1reg = new Area1AreaRegistration();
    var area1context = new AreaRegistrationContext(area1reg.AreaName, RouteTable.Routes);
    area1reg.RegisterArea(area1context);

    var area2reg = new Area2AreaRegistration();
    var area2context = new AreaRegistrationContext(area2reg.AreaName, RouteTable.Routes);
    area2reg.RegisterArea(area2context);

    var area3reg = new Area3AreaRegistration();
    var area3context = new AreaRegistrationContext(area3reg.AreaName, RouteTable.Routes);
    area3reg.RegisterArea(area3context);
}

Another option is to take the code for RegisterAllAreas(), copy it into your own app, and build your own mechanism for determining the order. It is quite a bit of code to copy if you want all the fancy caching logic that the built-in method does, but your app might not even need that.

Eilon
  • 25,103
  • 3
  • 82
  • 100
  • Great answer. Also, less code if you make a method and then it's one line of code per area. – CRice Jul 24 '16 at 12:21
7

Currently it's not possible to order areas. However, I think it makes sense to try and make each area as independent from other areas as possible so the order doesn't matter.

For example, instead of having the default {controller}/{action}/{id} route, maybe replace that with specific routes for each controller. Or add a constraint to that default route.

We are mulling over options to allow ordering, but we don't want to overcomplicate the feature.

Haacked
  • 54,591
  • 14
  • 86
  • 110
  • Hey Phil, thanks for the explanation. I ended up moving my "site" out of Areas and it is now the "default/non-area" section (with Views and Controllers in root). I then set my namespace paramater to the default controllers to avoid the "ambiguous controller" error. Also, I could have probably set constraints to something like ^((?!admin).*) to ignore the admin in the site area routes. I agree, adding more complexity isn't great, although it seems mvc 1 (non area) routes could potentially rely heavily on order. Thanks! – Jason Oct 29 '09 at 14:58
  • The routing process typically depends on the rules order, so the 'ordering' feature may be important. – twk Nov 03 '09 at 22:56
  • Please see my response for two techniques that allow you to order area registration (and thus their routes) in any order. – Eilon Jan 21 '10 at 07:40
  • all well and good, but this wasnt thought of in our current solution. now when we upgrade to VS2015 the compiler has inexplicably changed the order it adds Area registrations...breaking changes all round - a simple fix would have been to add the order (as an attribute? hardly complex), now we must use @Ellon's solution – jenson-button-event Oct 12 '15 at 16:16
4

For reference,

In MVC3 (don't know about MVC2) when you just want to map root to a specific area/controller you could simply use a global route. Just remember to specify the namespace/area.

    routes.MapRoute(
      "CatchRoot", "",
      new { controller = "SITEBLOG-CONTROLLER-NAME", action = "Index"} 
     ).DataTokens.Add("area", "SITE-AREA-NAME");
Simon
  • 2,239
  • 2
  • 17
  • 20
  • +1 - This is a nice solution because it allows everything to be grouped into areas but one area to serve as the catch-all with a cleaner URL. – Tim Medora Aug 13 '12 at 22:27
  • Works perfectly! Allows me to have all controllers in areas instead of 'special one' and actual areas. For anyone wanting the same, register default route in route config. – FDIM Apr 17 '14 at 09:14
4

I make this solution:

AreaUtils.cs

    using System;
    using System.Web.Mvc;
    using System.Web.Routing;

    namespace SledgeHammer.Mvc.Site
    {
        public static class Utils
        {
                public static void RegisterArea<T>(RouteCollection routes,
    object state) where T : AreaRegistration

            {
                 AreaRegistration registration =
     (AreaRegistration)Activator.CreateInstance(typeof(T));

                    AreaRegistrationContext context =
     new AreaRegistrationContext(registration.AreaName, routes, state);

                    string tNamespace = registration.GetType().Namespace;
                    if (tNamespace != null)
                {
                    context.Namespaces.Add(tNamespace + ".*");
                }

                registration.RegisterArea(context);
            }
        }

    }

In global.asax:

Utils.RegisterArea<SystemAreaRegistration>(RouteTable.Routes, null);
Utils.RegisterArea<ClientSitesAreaRegistration>(RouteTable.Routes, null);

//AreaRegistration.RegisterAllAreas(); do not dublicate register areas

No requred changes to generated area registration code. I also use custom constrant in routes to filter routes by type of domain in request (system domain or user site).

This is my area registrations as example:

namespace SledgeHammer.MVC.Site.Areas.System
{
    public class SystemAreaRegistration : AreaRegistration
    {
        public override string AreaName
        {
            get { return "System"; }
        }

        public override void RegisterArea(AreaRegistrationContext context)
        {
            context.MapRoute(
                "System_Feedback",
                "Feedback",
                new { controller = "Feedback", action = "Index" }
            );
            context.MapRoute(
                "System_Information",
                "Information/{action}/{id}",
                new { controller = "Information", action = "Index", id = UrlParameter.Optional }
            );
        }
    }
}



namespace SledgeHammer.MVC.Site.Areas.ClientSites
{
    public class ClientSitesAreaRegistration : AreaRegistration
    {
        public override string AreaName
        {
            get { return "ClientSites"; }
        }

        public override void RegisterArea(AreaRegistrationContext context)
        {
            context.MapRoute(
                "ClientSites_default",
                "{controller}/{action}/{id}",
                new { controller = "Site", action = "Index", id = UrlParameter.Optional },
                new { Host = new SiteInGroups("clients") }
            );
        }
    }
}
Kuvalda.Spb.Ru
  • 431
  • 3
  • 6
  • your util class is awesome because it points out you need to add the namespace for the areas otherwise the routs won't be linked to the controllers – Will Charczuk Oct 19 '11 at 20:08