views:

817

answers:

2

The website I'm working on has some fairly complicated routing structures and we're experiencing some difficulties working with the routing engine to build URLs the way we need them to be built.

We have a search results page that uses RegEx based pattern matching to group several variables into a single route segment (i.e. "www.host.com/{structuralParameters}" can be the following: "www.host.com/variableA-variableB-variableC" - where variables A through C are all optional). This is working for us fine after a bit of work.

The problem we are experiencing resolves around an annoying feature of the ActionLink method: if you point to the same controller/action it will retain the existing route values whether you want them or not. We prefer to have control over what our links look like and, in some cases, cannot have the existing parameters retained. An example would be where our site's main navigation leads to a search results page with no parameters set - a default search page, if you like. I say this is an annoying feature because it is a rare instance of the ASP.Net MVC Framework seemingly dictating implementation without an obvious extension point - we would prefer not to create custom ActionLink code to write a simple navigation link in our master page!

I've seen some say that you need to explicitly set such parameters to be empty strings but when we try this it just changes the parameters from route values into query string parameters. It doesn't seem right to me that we should be required to explicitly exclude values we aren't explicitly passing as parameters to the ActionLink method but if this is our only option we will use it. However at present if it is displaying in the query string then it is as useless to us as putting the parameters directly into the route.

I'm aware that our routing structure exasperates this problem - we probably wouldn't have any issue if we used a simpler approach (i.e. www.host.com/variableA/variableB/variableC) but our URL structure is not negotiable - it was designed to meet very specific needs relating to usability, SEO, and link/content sharing.

How can we use Html.ActionLink to generate links to pages without falling back on the current route data (or, if possible, needing to explicitly excluding route segments) even if those links lead to the same action methods?

If we do need to explicitly exclude route segments, how can we prevent the method from rendering the routes as query string parameters?

This seemingly small problem is causing us a surprising amount of grief and I will be thankful for any help in resolving it.

EDIT: As requested by LukLed, here's a sample ActionLink call:

// I've made it generic, but this should call the Search action of the 
// ItemController, the text and title attribute should say "Link Text" but there
// should be no parameters - or maybe just the defaults, depending on the route.
// 
// Assume that this can be called from *any* page but should not be influenced by
// the current route - some routes will be called from other sections with the same
// structure/parameters.
Html.ActionLink( 
    "Link Text",
    "Search", 
    "Item", 
    new { }, 
    new { title = "Link Text" } 
);

Cheers, Zac

A: 

If you want total control of the link, just build the link yourself:

<a href="~/variableA/variableB/<%= Html.Encode(Model.Target) %>">Click Here</a>

Substitute whatever you need inside the href attribute.

Robert Harvey
Yes, this is an option - and certainly one worth considering in the interest of saving time. My concern with this approach is that we may be required to change our routes in the future and it would be better to do this in the route dictionary than each individual view - even if there are only a few views affected. It leaves more opportunity for human error to lead to bugs... Regardless, it may be the best option in this case.
Zac
Well it sounds like you want it to follow only part of the route. To do that you would have to sift through the route dictionary and build an URL from that.
Robert Harvey
For the record, you may not need all of those slashes in the URL to get good SEO. Check out the URL in your browser for this page. StackOverflow SEO is as good as it gets.
Robert Harvey
Yeah, I'm starting to think I'll need to do something along the lines of what you've suggested with the route dictionary. Probably encapsulated within a custom ActionLink extension method. StackOverflow have done an excellent job at optimising their site - why else would they consistently be found at the top of my search results? ;-) They do, however, have a much clearer structure to the content on their site than we do - and we're not in a position to change that. From my understanding our URL strategy is sound (we've put a lot of work into this area recently) but it does complicate things.
Zac
+2  A: 

Setting route values to be null or empty string when calling Html.ActionLink or Html.RouteLink (or any URL generation method) will clear out the "ambient" route values.

For example, with the standard MVC controller/action/id route suppose you're on "Home/Index/123". If you call Html.RouteLink(new { id = 456 }) then MVC will notice the "ambient" route values of controller="Home" and action="Index". It will also notice the ambient route value of id="123" but that will get overwritten by the explicit "456". This will cause the generated URL to be "Home/Index/456".

The ordering of the parameters matters as well. For example, say you called Html.RouteLink(new { action = "About" }). The "About" action would overwrite the current "Index" action, and the "id" parameter would get cleared out entirely! But why, you ask? Because once you invalidate a parameter segment then all parameter segments after it will get invalidated. In this case, "action" was invalidated by a new expliciti value so the "id", which comes after it, and has no explicit value, also gets invalidated. Thus, the generated URL would be just "Home/About" (without an ID).

In this same scenario if you called Html.RouteLink(new { action = "" }) then the generated URL would be just "Home" because you invalidated the "action" with an empty string, and then that caused the "id" to be invalidated as well because it came after the invalidated "action".

Eilon
@Eilon i can't reproduce this behavior u told in the post. i have Html.ActionLink("abc", "actionA",new{docid= 4, id=5}) and on this page when i render link Html.ActionLink("xyz", "actionB" , new{id=90}) as per ur explanation it should clear docid because i changed action parameter but it does not clear docid and use it in link generation. i m having hell a lot of trouble in this ambient value problem. using .NET 4 with mvc2
Muhammad Adeel Zahid
@Muhammad, the parameter clearing stuff depends on the route that ended up getting selected for generation. If in that route the {docid} parameter occurs before other parameters that were changed then the ambient value will remain. If you can share your code I can investigate this. I suggest opening a new question on StackOverflow and just link to it from here (make sure to mention @Eilon in the comment so that it notifies me!).
Eilon
@Eilon yes the behavior u told is correct. just, i was mistaken a little bit. i have posted my code here along with question at http://stackoverflow.com/questions/3475981/ambient-values-in-mvc2-net-routing. plz have a look
Muhammad Adeel Zahid