encoding returnUrl query string in ASP.NET MVC

Kamyar picture Kamyar · Dec 31, 2011 · Viewed 8.1k times · Source

I'm trying to pass ReturnUrl query string to my login page. I have a partial Login view (kind of like a login box) and a login view. Right now, I have the following line in my partial view:

using (Html.BeginForm("Login"
                    , "Account"
                    , new { returnUrl=HttpUtility.UrlEncode(
                                      Request.QueryString["ReturnUrl"]) 
                                      ?? HttpUtility.UrlEncode(Request.Path)
                           }
                    , FormMethod.Post))

When I click on my partial view's submit button(e.g. from http://localhost:12345/Product/1234/Product1),

I'll get redirected to my login page with the following url:

http://localhost:12345/Account/Login?returnUrl=%2FProduct%2F1234%2FProduct1  

But if I log on, I'll see an http 400 response, because the page is returning to

http://localhost:12345/Account/%2fProduct%2f1234%2fproduct1.

Also, if I type in the wrong credentials, I cause the returnUrl query string to be encoded again thus every % character is converted to %25 again!

I should mention if I manually type in

http://localhost:12345/Account/Login?returnUrl=/Product/1234/Product1

(without encoding) and log in, I'm successfully redirected to the specified path.

I guess I'm missing something obvious. But couldn't find out what.

Thanks.

Answer

Russ Cam picture Russ Cam · Dec 31, 2011

You don't need to encode it when you pass it to Html.BeginForm() Just use the value without encoding it as it is encoded by the TagBuilder when assigning the value to the action attribute of the generated <form> element

@using(Html.BeginForm(
    "Login", 
    "Account", 
    new { returnUrl= Request.QueryString["ReturnUrl"] }, 
    FormMethod.Post)) {

    @* form here *@    
}

I've removed the null-coalescing operator and Request.Path too because if ReturnUrl is null, it's not going to matter to the generated url assigned to the action attribute (assuming it's an optional parameter in your routing, or an optional property on your ViewModel).

The relevant source code in TagBuilder is (comment mine)

private void AppendAttributes(StringBuilder sb) {
    foreach (var attribute in Attributes) { 
        string key = attribute.Key;
        if (String.Equals(key, "id", StringComparison.Ordinal /* case-sensitive */) && String.IsNullOrEmpty(attribute.Value)) { 
            continue; // DevDiv Bugs #227595: don't output empty IDs 
        }

        // ---------------------------------------
        // Value will be HTML attribute encoded by 
        // the Encoder set for the application
        // ---------------------------------------
        string value = HttpUtility.HtmlAttributeEncode(attribute.Value); 
        sb.Append(' ')
          .Append(key)
          .Append("=\"")
          .Append(value) 
          .Append('"');
    } 
}