17

I have the following HtmlHelper method that I want to create a button that does a redirect with JavaScript:

public static string JavaScriptButton(this HtmlHelper helper, string value,
                        string action, string controller, object routeValues = null, object htmlAttributes = null)
{
    var a = (new UrlHelper(helper.ViewContext.RequestContext))
                            .Action(action, controller, routeValues);

    var builder = new TagBuilder("input");
    builder.Attributes.Add("type", "submit");
    builder.Attributes.Add("value", value);
    builder.Attributes.Add("class", "button");
    builder.Attributes.Add("onclick", string.Format("javascript:location.href='{0}'", a));
    builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));

    return MvcHtmlString.Create(builder.ToString(TagRenderMode.SelfClosing)).ToString();         
}

The problem is that the line that creates the onclick handler is getting escaped by the tagbuilder, the resulting html is:

<input class="button" onclick="javascript:location.href=&#39;&#39;" type="submit" value="Return to All Audits" />

Is there anyway I can stop this behaviour?

Bill the Lizard
  • 398,270
  • 210
  • 566
  • 880
dagda1
  • 26,856
  • 59
  • 237
  • 450

4 Answers4

14

This is actually an issue with .NET 4.0. To fix it you need to override the attribute encoding process.

public class HtmlAttributeNoEncoding : System.Web.Util.HttpEncoder
{
    protected override void HtmlAttributeEncode(string value, System.IO.TextWriter output)
    {
        output.Write(value);
    }
}

Then put this in your web.config file under the <system.web> element:

<httpRuntime encoderType="HtmlAttributeNoEncoding"/>

I found this here.

Ryan
  • 4,303
  • 3
  • 24
  • 24
9

While the solution provided by Ryan may work, it is a little like using a hammer to swat a fly.

The issue is that TagBuilder encodes strings during calls to MergeAttributes(). For the required Javascript in a button link this affects single quotes and spaces.

The last step of the required extension method is the return of an MvcHtmlString (which receives no further encoding), so it is perfectly reasonable to make some simple text corrections to the string (to undo the encoding) before creating that object.

e.g.

return new MvcHtmlString(tb.ToString(TagRenderMode.Normal).Replace("&#39;", "\'").Replace("&#32;"," "));

A complete ActionLinkButton helper is shown below:

public static class ActionLinkButtonHelper
{
    public static MvcHtmlString ActionLinkButton(this HtmlHelper htmlHelper, string buttonText, string actionName, object routeValuesObject = null, object htmlAttributes = null)
    {
        return ActionLinkButton(htmlHelper, buttonText, actionName, "", routeValuesObject, htmlAttributes);
    }
    public static MvcHtmlString ActionLinkButton(this HtmlHelper htmlHelper, string buttonText, string actionName, string controllerName, object routeValuesObject = null, object htmlAttributes = null)
    {
        if (string.IsNullOrEmpty(controllerName))
        {
            controllerName = HttpContext.Current.Request.RequestContext.RouteData.Values["controller"].ToString();
        }
        RouteValueDictionary routeValues = new RouteValueDictionary(routeValuesObject);
        RouteValueDictionary htmlAttr = HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes);
        TagBuilder tb = new TagBuilder("button");
        tb.MergeAttributes(htmlAttr, false);
        string href = UrlHelper.GenerateUrl("default", actionName, controllerName, routeValues, RouteTable.Routes, htmlHelper.ViewContext.RequestContext, false);

        tb.MergeAttribute("type", "button");
        tb.SetInnerText(buttonText);
        tb.MergeAttribute("value", buttonText);
        tb.MergeAttribute("onclick", "location.href=\'"+ href +"\';return false;");
        return new MvcHtmlString(tb.ToString(TagRenderMode.Normal).Replace("&#39;", "\'").Replace("&#32;"," "));
    }
}

This does everything you need to add button links, has the most useful overloads you use with ActionLink and does not potentially cause unexpected application-wide changes by changing the attribute encoding process.

iCollect.it Ltd
  • 92,391
  • 25
  • 181
  • 202
0

The TagBuilder does not encode the string during MergeAttribute as directly manipulating the Attributes property and then getting the string representation with ToString() is also escaping the characters. Most probably the encoding happens during ToString().

The class below solves the problem with JavaScript:

public class JavaScriptTagBuilder : TagBuilder
{
    public string OnClick { get; set; }

    public JavaScriptTagBuilder(string tagName)
        : base(tagName)
    {
    }

    public override string ToString()
    {
        string openingTag = "<" + TagName;
        return base.ToString().Replace(openingTag, string.Format("{0} onclick=\"{1}\"", openingTag, OnClick));
    }
}
Alex Filatov
  • 2,232
  • 3
  • 32
  • 39
0

I'm a little late to the disco but you could decode the HTML before returning it:

return new MvcHtmlString(System.Web.HttpUtility.HtmlDecode(tb.ToString(TagRenderMode.Normal));