2

I am having trouble making @Url.Action work with Area's that have a non standard route structure.

For instance if I register this route in my Dashboard area:

        context.MapRoute(
            "Dashboard_default",
            "Dashboard/{controller}/{action}/{id}",
            new { controller = "View", action = "Display", id = UrlParameter.Optional }
        );

and then in my layout view I call:

        @Url.Action("Select", "View", new { area = "Dashboard" })

I get a proper url: /Dashboard/View/Select

However, if I change the route to include an optional secondary id like this:

        context.MapRoute(
            "Dashboard_default",
            "Dashboard/{controller}/{action}/{id}/{secondaryid}",
            new { controller = "View", action = "Display", id = UrlParameter.Optional, secondaryid = UrlParameter.Optional }
        );

Then the same call to @Url.Action(...) doesn't return any url. If I specify those optional parameters with real values like so:

        @Url.Action("Select", "View", new { area = "Dashboard", id = 1, secondaryid = 2 })

I do get a god return value of: /Dashboard/View/Select/1/2

The problem is that for some of my actions in this area don't need the id or secondary id and I want the url to be generated without them. If i set them to (int?)null it still doesn't work.

Am I doing something wrong? Shouldn't Url.Action(...) return the URL without the id and secondaryid tokens if I dont specify them in the routeValues parameter?

Felipe Oriani
  • 37,948
  • 19
  • 131
  • 194
Mr Bell
  • 9,228
  • 18
  • 84
  • 134

1 Answers1

3

Having multiple optional parameters does funky things to your routes. Basically, the route engine cannot (has trouble) matching one or no optional parameters where there is a group of them. For more information, check out this blog post on the same issue.

Since you don't always need id or secondary id, just make a couple of routes to handle those cases.

context.MapRoute(
            "Dashboard_IdAndSecondaryId",
            "Dashboard/{controller}/{action}/{id}/{secondaryid}",
            new { controller = "View", action = "Display"}
        );
context.MapRoute(
            "Dashboard_default_WithSecondaryId",
            "Dashboard/{controller}/{action}/{secondaryid}",
            new { controller = "View", action = "Display"}
        );

context.MapRoute(
            "Dashboard_default",
            "Dashboard/{controller}/{action}/{id}/",
            new { controller = "View", action = "Display", id = UrlParameter.Optional}
        );

Now, when you send just an Id, just a secondaryId or both, you will have routes that will match. We can remove the optional parameter declarations in the first two routes, because in order to match that route, you would have to send the required parameters. Without sending the required parameters, you would want only the last route to match.

The last route is your default route when only Id or none is sent in the action link. I believe that this order works as well, keeping in mind you want your routes to go from most specific to least specific given that they are processed in order.

Tommy
  • 39,592
  • 10
  • 90
  • 121
  • This worked except I had to change the middle route to be with id instead of secondary id and then change the last one to be without id or secondary id. so the route matching goes: with id and secondary id, with only id, and lastly no id at all – Mr Bell Nov 30 '12 at 18:05
  • 1
    You may be able to make this only two routes then if you never only send a secondaryId by itself. Basically, delete my second route. Then the default will take a route with just an id or no parameters, clean up the route table just a tad bit more. Anyways, glad it worked out for you! – Tommy Nov 30 '12 at 18:10