Adam Lassek Adam Lassek - 4 months ago 52
HTML Question

How can I use the button tag with ASP.NET?

I'd like to use the newer

<button>
tag in an ASP.NET website which, among other things, allows CSS-styled text and embedding a graphic inside the button. The asp:Button control renders as
<input type="button">
, is there any way to make a preexisting control render to
<button>
?

From what I've read there is an incompatibility with IE posting the button's markup instead of the value attribute when the button is located within a
<form>
, but in ASP.NET it will be using the onclick event to fire __doPostBack anyway, so I don't think that this would be a problem.

Are there any reasons why I shouldn't use this? If not, how would you go about supporting it with asp:Button, or a new server control based on it? I would prefer to not write my own server control if that can be avoided.




At first the
<button runat="server">
solution worked, but I immediately ran into a situation where it needs to have a CommandName property, which the HtmlButton control doesn't have. It looks like I'm going to need to create a control inherited from Button after all.

What do I need to do in order to override the render method and make it render what I want?




UPDATE

DanHerbert's reply has made me interested in finding a solution to this again, so I've spent some more time working on it.

First, there's a far easier way of overloading the TagName:

public ModernButton() : base(HtmlTextWriterTag.Button)
{
}


The problem with Dan's solution as it stands is the innerhtml of the tag is placed into the value property, which causes a validation error on postback. A related problem is, even if you render the value property correctly, IE's braindead implementation of the
<button>
tag posts the innerhtml instead of the value anyway. So, any implementation of this needs to override the AddAttributesToRender method in order to correctly render the value property, and also provide some sort of workaround for IE so it doesn't completely screw up the postback.

The IE problem may be insurmountable if you want to take advantage of the CommandName/CommandArgument properties for a databound control. Hopefully someone can suggest a workaround for this.

I have made progress on the rendering:

ModernButton.cs

This renders as a proper html
<button>
with the correct value, but it doesn't work with the ASP.Net PostBack system. I've written some of what I need to provide the
Command
event, but it doesn't fire.

When inspecting this button side-by-side with a regular asp:Button, they look the same other than the differences I need. So I'm not sure how ASP.Net is wiring up the
Command
event in this case.

An additional problem is, nested server controls aren't rendered (as you can see with the ParseChildren(false) attribute). It's pretty easy to inject literal html text into the control during render, but how do you allow support for nested server controls?

Answer

This is an old question, but for those of us unlucky enough still having to maintain ASP.NET Web Forms applications, I went through this myself while trying to include Bootstrap glyphs inside of built-in button controls.

As per Bootstrap documentation, the desired markup is as follows:

<button class="btn btn-default">
    <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
    Search
</button>

I needed this markup to be rendered by a server control, so I set out to find options.

Button

This would be the first logical step, but —as this question explains— Button renders an <input> element instead of <button>, so adding inner HTML is not possible.

LinkButton (credit to Tsvetomir Tsonev's answer)

Source

<asp:LinkButton runat="server" ID="uxSearch" CssClass="btn btn-default">
    <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
    Search
</asp:LinkButton>

Output

<a id="uxSearch" class="btn btn-default" href="javascript:__doPostBack(&#39;uxSearch&#39;,&#39;&#39;)">
    <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
    Search
</a>

Pros

  • Looks OK
  • Command event; CommandName and CommandArgument properties

Cons

  • Renders <a> instead of <button>
  • Renders and relies on obtrusive JavaScript

HtmlButton (credit to Philippe's answer)

Source

<button runat="server" id="uxSearch" class="btn btn-default">
    <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
    Search
</button>

Result

<button onclick="__doPostBack('uxSearch','')" id="uxSearch" class="btn btn-default">
    <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
    Search
</button>

Pros

  • Looks OK
  • Renders proper <button> element

Cons

  • No Command event; no CommandName or CommandArgument properties
  • Renders and relies on obtrusive JavaScript to handle its ServerClick event

At this point it is clear that none of the built-in controls seem suitable, so the next logical step is try and modify them to achieve the desired functionality.

Custom control (credit to Dan Herbert's answer)

NOTE: This is based on Dan's code, so all credit goes to him.

using System.Web.UI;
using System.Web.UI.WebControls;

namespace ModernControls
{
    [ParseChildren]
    public class ModernButton : Button
    {
        public new string Text
        {
            get { return (string)ViewState["NewText"] ?? ""; }
            set { ViewState["NewText"] = value; }
        }

        public string Value
        {
            get { return base.Text; }
            set { base.Text = value; }
        }

        protected override HtmlTextWriterTag TagKey
        {
            get { return HtmlTextWriterTag.Button; }
        }

        protected override void AddParsedSubObject(object obj)
        {
            var literal = obj as LiteralControl;
            if (literal == null) return;
            Text = literal.Text;
        }

        protected override void RenderContents(HtmlTextWriter writer)
        {
            writer.Write(Text);
        }
    }
}

I have stripped the class down to the bare minimum, and refactored it to achieve the same functionality with as little code as possible. I also added a couple of improvements. Namely:

  • Remove PersistChildren attribute (seems unnecessary)
  • Remove TagName override (seems unnecessary)
  • Remove HTML decoding from Text (base class already handles this)
  • Leave OnPreRender intact; override AddParsedSubObject instead (simpler)
  • Simplify RenderContents override
  • Add a Value property (see below)
  • Add a namespace (to include a sample of @ Register directive)
  • Add necessary using directives

The Value property simply accesses the old Text property. This is because the native Button control renders a value attribute anyway (with Text as its value). Since value is a valid attribute of the <button> element, I decided to include a property for it.

Source

<%@ Register TagPrefix="mc" Namespace="ModernControls" %>

<mc:ModernButton runat="server" ID="uxSearch" Value="Foo" CssClass="btn btn-default" >
    <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
    Search
</mc:ModernButton>

Output

<button type="submit" name="uxSearch" value="Foo" id="uxSearch" class="btn btn-default">
    <span class="glyphicon glyphicon-search" aria-hidden="true"></span>
    Search
</button>

Pros

  • Looks OK
  • Renders a proper <button> element
  • Command event; CommandName and CommandArgument properties
  • Does not render or rely on obtrusive JavaScript

Cons

  • None (other than not being a built-in control)
Comments