17

The question is similar to asp.net mvc Html.ActionLink() keeping route value I don't want, but with a twist that makes it more complex.

Starting from a default new MVC3 app, I change the routes to:

routes.MapRoute(
    "r1", // Route name
    "{controller}/{id}/{action}"
);

routes.MapRoute(
    "r2", // Route name
    "{controller}/{action}"
);

Notice that the id comes before the action in the first.

Then in Home\Index.cshtml, I add:

@Url.Action("Index")
@Url.Action("Index", new { id = "blah" })
@Url.Action("Index", new { id = "" })

Now I navigate to /Home/Foo/Index and look at the 3 generated links. I get

  1. "/Home/Foo/Index"
  2. "/Home/blah/Index"
  3. "/Home/Index?id=Foo"

The first two make sense, and are using the first route.

But in the third link, which hits the second route, I don't understand why id=Foo is passed on the query string, given that I explicitly passed an empty id. I would expect it to just generate "/Home/Index".

Can anyone explain that, and suggest how I can get it not to show up?

Community
  • 1
  • 1
David Ebbo
  • 42,443
  • 8
  • 103
  • 117

4 Answers4

20

I just tested this and it seems to work ok.

Url.Action("Index", new { id = UrlParameter.Optional })

generates

/Home/Index
PabloBlamirez
  • 2,692
  • 3
  • 18
  • 14
  • 1
    Had this very problem, your solution worked but I am still getting a URL something like: "/Home/Index?id=" Not EXACTLY what I wanted, but close enough I guess.. – Porco Dec 01 '11 at 18:52
6

To solve it in this case is quite easy if yo don't care how it's solved.

Change the third link to:

@Url.RouteUrl("R2")

which gives me:

  • /Home/Foo/Index
  • /Home/blah/Index
  • /Home/Index

Why it happends in the first case is something I have yet to figure out. I have been bitten by this too but mostly when it comes to forms.

UPDATE 1:

I was digging around the MVC source and created a few tests that reproduced this problem. The problem seems to be when

RouteCollection.GetVirtualPath(requestContext, correctedValues);

is run. It creates a querystring with the old id. Exactly why I couldn't tell as that class isn't located in the MVC source.

Mikael Eliasson
  • 5,157
  • 23
  • 27
  • +1 for the workaround. But I'd be definitely interested in a way which doesn't require to directly involve a route – nulltoken Jun 30 '11 at 05:53
  • Yes, this does seem to work, though it's a bit dirty. In the real scenario, I actually want to go to a different action, so I have to pass that explicitly to RouteUrl, e.g. new { Action = "SomeAction" }. But unless someone comes up with something better, I'll give you the check mark on this one :) – David Ebbo Jun 30 '11 at 06:08
  • What's puzzling is that even when it wasn't working, it was already hitting the correct "R2" route (I think). So it seems RouteUrl has a side benefit of ignoring ambient values, which is what makes it work. – David Ebbo Jun 30 '11 at 06:23
  • Problem with removing the value is that it might affect other links you generate afterwards – David Ebbo Jun 30 '11 at 06:36
  • True, I removed that part so noone uses it and make their colleauges go insane. – Mikael Eliasson Jun 30 '11 at 06:49
6

This is probably not the answer you're looking for, but using Url.Route instead of Url.Action seems to work:

@Url.RouteUrl("r2")

This generates /Home/Index

Update

Phil Haack recommendeds generating URLs using the route name:

This might seem like a big problem, but the fix is actually very simple. Use names for all your routes and always use the route name when generating URLs. Most of the time, letting routing sort out which route you want to use to generate an URL is really leaving it to chance. When generating an URL, you generally know exactly which route you want to link to, so you might as well specify it by name.

Daniel Liuzzi
  • 16,807
  • 8
  • 52
  • 57
  • Thanks Daniel, that does lead to a working solution, albeit a bit dirty. Mikael beat you to it by a couple minutes on it :) – David Ebbo Jun 30 '11 at 06:09
  • Yeah, I've seen it. I was going to delete my answer when I saw his, but then I remembered about Phil's post and decided to leave it to include the quote, for context. Glad to help :) – Daniel Liuzzi Jun 30 '11 at 06:19
  • 1
    And about the dirtiness... I remember thinking this was a dirty solution when I first read Phil's post. But I later changed my mind and now think using the route name is actually a good idea. It also gives you the added benefit of being able to rename/move your controllers and actions to your heart's contents, without having to worry about changing all your views. Cheers. – Daniel Liuzzi Jun 30 '11 at 06:27
1

Because r2 does not define the id parameter to be a part of the URL? If you change it to {controller}/{action}/{id}, it will be fine. You'll see the same behaviour if you change r2 to not include action: action will then also becoe a query variable.

The fact that you explicitly set it to "" means that routing will take it into account anyway. Just don't add id and you'll be fine when generating a link.

maartenba
  • 3,344
  • 18
  • 31
  • If I don't add 'id' at all, then it's case #1 above, which returns the wrong thing (uses the first route). – David Ebbo Jun 30 '11 at 05:35
  • And I can't change r2 to {controller}/{action}/{id} without passing an id. Unless you meant also give it a default value? Seems quirky to do anything with ID when the route doesn't need it at all. I just want to clear it altogether at the time I build the link. – David Ebbo Jun 30 '11 at 05:40
  • 1
    Routing will always add route values to the URLs generated as a convention (which I cursed on a lot building the MVC SiteMapProvider...). If you want to use an specific route, use the route name (Url.RouteUrl) instead and don't let routing "guess" the route based on what you provided. I agree it's not what you would expect from the framework but changing this behaviour will probably lead to more confusion. – maartenba Jun 30 '11 at 06:00
  • Yes, RouteUrl seems to help here – David Ebbo Jun 30 '11 at 06:11