views:

887

answers:

6

I've noticed the returnurl URL parameter on the Stackoverflow login/logout links are not escaped but when I try to add path as a parameter to a route it gets escaped.

So /login?returnurl=/questions/ask shows /login?returnurl=%2fquestions%2fask and it's kind of ugly. How do I get it to not escape the returnurl value?

Here's what I'm doing in the code:

Html.ActionLink("Login", "Login", "Account", new { returnurl=Request.Path }, null)
A: 

My solutionto a similar problem was to write my own extension. After digging around in the code I couldn't find a way to do it otherwise. Yours might look like this.

public static class HtmlHelperExtensions
{
    public static string LoginLinkWithReturnUrl( this HtmlHelper helper,
                                                 string linkText,
                                                 string action,
                                                 string controller,
                                                 string returnUrl,
                                                 object htmlAttributes )
    {
         TagBuilder builder = new TagBuilder("a");
         builder.Attributes.Add( "href",
                                  string.Format( "/{0}/{1}?returnurl={2}",
                                                 controller,
                                                 action,
                                                 returnUrl ) );
         var attrDict = new RouteValueDictionary( htmlAttributes );
         builder.MergeAttributes( attrDict );
         builder.InnerHtml = linkText;
         return builder.ToString();
    }
}

I think I had the same problem making and using a UrlHelper so I went with the string.Format mechanism instead. YMMV.

tvanfosson
The only problem with this is that you are hard-coding the route, which means this will break if you aren't using the default route.
JMs
It was just an example off the top of my head. Probably you could use the UrlHelper to build the route from the action/controller and then append the query parameters to the end of it.
tvanfosson
A: 

How do I get it to not escape the returnurl value

How's about this?

var url = Url.Action("Login", "Account", new {returnurl = Request.Path});
var unEncodedUrl = HttpUtility.UrlDecode(url);
Response.Write("<a href='" + unEncodedUrl + "'>...</a>");

Be sure that's what you want though, URL encoding has its purpose.

Buu Nguyen
A: 

I don't believe there's a way around it that's built into the framework. The actual construction of the URL happens in the System.Web.Routing.ParsedRoute.Bind method and there aren't any conditions used to prevent the escaping.

Looks like an extension method is the way to go but one that is slightly more robust than the one mentioned previously.

Todd
A: 

I understand one of the comments about encoding happening for a reason; this would only be an exception, not the rule.

Here's what I put together, how can it be improved?

    public static string ActionLinkNoEscape(this HtmlHelper html, string linkText, string actionName, string controllerName, object values, object htmlAttributes)
    {
        RouteValueDictionary routeValues = new RouteValueDictionary(values);
        RouteValueDictionary htmlValues = new RouteValueDictionary(htmlAttributes);

        UrlHelper urlHelper = new UrlHelper(html.ViewContext.RequestContext, RouteTable.Routes);
        string url = urlHelper.Action(actionName, controllerName);
        url += "?";
        List<string> paramList = new List<string>();
        foreach (KeyValuePair<string, object> pair in routeValues)
        {
            object value = pair.Value ?? "";
            paramList.Add(String.Concat(pair.Key, "=", Convert.ToString(value, CultureInfo.InvariantCulture)));
        }
        url += String.Join("&", paramList.ToArray());

        TagBuilder builder = new TagBuilder("a");
        builder.InnerHtml = string.IsNullOrEmpty(linkText) ? "" : HttpUtility.HtmlEncode(linkText);
        builder.MergeAttributes<string, object>(htmlValues);
        builder.MergeAttribute("href", url);
        return builder.ToString(TagRenderMode.Normal);
    }
Todd
+1  A: 

The parameter is not unescaped. You'll notice the URL:

http://stackoverflow.com/users/login?returnurl=%2fquestions%2fask

does actually work - SO is reading and unescaping that parameter as normal. If you wanted to include other out-of-bounds characters such as '&' in the parameter you would still have to escape them.

The trick is merely that the '/' character in particular does not need to be %-escaped in query parameters. It does have to be escaped in other contexts such as in a path part, so URLEncode always encodes it, to be safe.

If you just want the URL to look prettier, simply escape the parameter as normal (which you must do to escape all the other characters that must be handled correctly), and then do a string replace on '%2f' with '/'.

bobince
I know the URL will work but I don't like the look, apparently Jeff and crew at SO felt the same way. This solution is too much of a hack.
Todd
It's guaranteed to remain a valid URL if you only unescape %2F in parameters. Your code above will fall over on any other character that isn't valid in parameters, eg. or %.
bobince
Like I said, it's an exception and not the rule.
Todd
A: 

Салют! Мне доставляет удовольствие почитывать ваши писания, стремлюсь помногократнее навещать ваш блог, да вы нечасто строчите. Каким-нибудь видом можно доставать новости вашего блога на электронную-почту?