Rick Strahl's Weblog  

Wind, waves, code and everything in between...
.NET • C# • Markdown • WPF • All Things Web
Contact   •   Articles   •   Products   •   Support   •   Advertise
Sponsored by:
West Wind WebSurge - Rest Client and Http Load Testing for Windows

Url.Action() and RouteValue Encoding


:P
On this page:

As I’m working though my first MVC application I’ve run into a few problems with creating URLs based on ‘special characters’. Here’s a specific example, where I need to create links from a list of tags. In the example above a list like C#,ASP.NET,MVC is turned into a list of links that looks like this:

Tag List

The tags are stored as a simple comma delimited string and are split and turned into links using the UrlHelper’s Action() method. The following is an extension method on my application specific UrlHelperExtensions class:

public static string GetTagLinkList(this UrlHelper urlHelper, string tags)
{
    if (string.IsNullOrEmpty(tags))
        return string.Empty;

    string[] tagStrings = tags.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries);

    StringBuilder html = new StringBuilder();
    foreach (string tagString in tagStrings)
    {
        string urlAction = urlHelper.Action("List", "Snippet", 
                                             new { 
                                                 listAction = "tag", 
                                                 listFilter = tagString.Trim()
                                             });
        html.Append(StringUtils.Href(HttpUtility.HtmlEncode(tagString.Trim()), urlAction) + ", ");
    }

    if (html.Length > 2)
        html.Length -= 2;

    return html.ToString();
}

The Action() method works against the available routes for the application and makes it easy and consistent to link to other Urls in your applications without explicitly building up URLs which is nice. So rather than writing out the URL you can specify the controller and action, plus any additional parameters in the RouteValues parameter and the Action() method figures out based on the route configuration how the URL needs to be formatted.

This works great – until you hit certain special characters in your URL. Specifically in the tag list above one of the tags is C# – notice the hash tag. A hashtag has special meaning in a URL that is meant to jump to the name of an anchor contained within the content of the page.

So what happens above when the C# link is embedded? It gets embedded raw as http://codepaste.net/list/tag/C# without any encoding. This actually works, but only coincidentally – when the MVC application parses the URL to perform it’s routing the value for the language (C#) gets truncated to C because the # is treated as a URL artifact rather than part of the actual embedded tag. Bummer!

So my first thought was – well duh – I’ll just UrlEncode the value myself:

string urlAction = urlHelper.Action("List", 
new {
listAction = "tag",
listFilter = urlHelper.Encode(tagString.Trim())
   });

but that doesn’t actually work because Action() actually DOES encode values on its own. It just doesn’t encode # as it should. The result of the code above is

C%2523

which is double encoded C%23. Not quite what I had in mind.

This came up in a Twitter discussion and some argument ensued over whether the # should be encoded or not, but I’m pretty adamant in that it definitely should be: Although you may want a hashtag in a URL somewhere, the # has no place in an action parameter, so when Action() parses parameters it should most definitely UrlEncode() the entire value no exceptions.

In the end the only way I could figure out to make this work is fall back on manually creating the URL as a string:

string urlAction = urlHelper.Content("~/list/tag/") + urlHelper.Encode(tagString.Trim());

This correctly produces the following URL:

http://codepaste.net/list/tag/C%23

which works correctly although it doesn’t look very pretty.

The code above of course then also loses the ability to check routes and build the URL from the configured routes, so if routes are changed this code will have to be updated. OTOH, it’s probably more efficient to just write out the hardcoded string in this way. :-}

To Encode or Not To Encode

So, does this behavior of the Action() method strike anybody else as incorrect? I don’t see any reason that something like C# as an action parameter should ever not encode. While hashtags on URLs are certainly valid, they need to always be used at the end of a request so I can’t imagine a scenario where the # should be embedded as part of Action parameters. What do you think?

Posted in MVC  ASP.NET  

The Voices of Reason


 

DotNetKicks.com
July 19, 2009

# Url.Action() and RouteValue Encoding

You've been kicked (a good thing) - Trackback from DotNetKicks.com

Haacked
July 19, 2009

# re: Url.Action() and RouteValue Encoding

I think it should be encoded as %23. the reason it isn't is because we rely on a method of the URL class for the encoding that might be the wrong method to call in this context. I believe we'll fix this in ASP.NET MVC 2.

Jason Haley
July 20, 2009

# Interesting Finds: July 20, 2009

Interesting Finds: July 20, 2009

Duncan Smart
July 20, 2009

# re: Url.Action() and RouteValue Encoding

My hunch is that somewhere, something is calling Uri.ToString() and not Uri.AbsoluteUri, which is something that bit us ages ago. For some reason Uri.ToString outputs an unescaped ("human readable"?) version of the querystring, thereby decoding carefully URL-encoded values.

Richard Deeming
July 20, 2009

# re: Url.Action() and RouteValue Encoding

Actually, it's more likely that the Bind method in the internal ParsedRoute class is calling Uri.EscapeUriString [1] instead of Uri.EscapeDataString [2]; the former is used to escape an entire URI, whereas the latter is used to escape part of a URI. This is similar to the encodeURI and encodeURIComponent methods in JavaScript.

For example:

Console.WriteLine(Uri.EscapeUriString("C++/C#"));
// C++/C#

Console.WriteLine(Uri.EscapeDataString("C++/C#"));
// C%2B%2B%2FC%23


It's good to hear that this will probably be fixed in v2.


[1] http://msdn.microsoft.com/en-us/library/system.uri.escapeuristring.aspx
[2] http://msdn.microsoft.com/en-us/library/system.uri.escapedatastring.aspx

Khalid Abuhakmeh
July 20, 2009

# re: Url.Action() and RouteValue Encoding

instead of trying to encode the # in C# why don't you just interpret # to the word "sharp".

so your web address wouldn't look like

http://codepaste.net/list/tag/C%23

but instead

http://codepaste.net/list/tag/Csharp

do the same for any other special character. "+" is "plus," "-" is "minus" or "dash", and "=" is "equal(s)." I feel preserving the web address readability is completely worth the effort of writing an interpreter.

Rick Strahl
July 20, 2009

# re: Url.Action() and RouteValue Encoding

@Khalid - not sure if that really helps. While it may make the URL more readable (which doesn't really matter), it doesn't make the URL more hackable to a power user. A power user would expect C# to work so they'd likely try http://codepaste.net/list/tag/C# and end up with the same original problem.

Like the idea of translating parameters though!

Chris Love
July 23, 2009

# re: Url.Action() and RouteValue Encoding

This is not really SEO freindly. First 'normal' users are not going to understand a URL Encoded character. Second from what I know search engines are not going to take the time to translate the %23 to #. If you think about it they cannot just assume something has actually been encoded. I like your example because it really shows this problem I have been raising for several months now. I typically use an 's to break the routing.

I do hope to create a blog in the coming days to show my position on this issue and it goes beyond just the Routing system we now have, it really affects any URL rewriting solution that solely relies on regular expressions. The real issue is how to rehydrate to the real URL correctly, while making a truely friendly URL that gets the highest SEO juice you can.

DotNetShoutout
July 28, 2009

# Url.Action() and RouteValue Encoding - Rick Strahl

Thank you for submitting this cool story - Trackback from DotNetShoutout

Boersnoes
August 03, 2009

# re: Url.Action() and RouteValue Encoding

I too encountered this problem a while ago.
ref: http://stackoverflow.com/questions/240224/double-incomplete-parameter-url-encoding
It's good to read that the team is on it and it will be fixed in the 2.0 version.

Krisztian
March 18, 2013

# re: Url.Action() and RouteValue Encoding

It looks like that as of MVC 4.0 this is still not changed. Some characters are still not encoded using Url.Action(...).

bgx
September 07, 2016

# re: Url.Action() and RouteValue Encoding

Just realized that apostrophes are not encoded either, which messes up attempts to use generated urls in JavaScript calls.

I have the following:
  <button type="button" class="btn btn-default" onclick="go('@Url.Action("Details", "Users", routeValues)');return false; ">Cancel</button>


The route values contain the user's email address. If this contains an apostrophe the JavaScript parameter gets wrongly truncated, producing something like
 <button type="button" class="btn btn-default" onclick="go('Details/User/user'a@example.com');return false; ">Cancel</button>


As a rough fix replacing apostrophes in the generated link works, but this is truly awkward:
  <button type="button" class="btn btn-default" onclick="go('@Url.Action("Details", "Users", routeValues.Replace("'","%27"))');return false; ">Cancel</button>

West Wind  © Rick Strahl, West Wind Technologies, 2005 - 2024