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

84,247

Solution 1

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)

Solution 2

Although you say that using the [button runat="server"] is not a good enough solution it is important to mention it - a lot of .NET programmers are afraid of using the "native" HTML tags...

Use:

<button id="btnSubmit" runat="server" class="myButton" 
    onserverclick="btnSubmit_Click">Hello</button>

This usually works perfectly fine and everybody is happy in my team.

Solution 3

I stumbled upon your question looking for the same exact thing. I ended up using Reflector to figure out how the ASP.NET Button control is actually rendered. It turns out to be really easy to change.

It really just comes down to overriding the TagName and TagKey properties of the Button class. After you've done that, you just need to make sure you render the contents of the button manually since the original Button class never had contents to render and the control will render a text-less button if you don't render the contents.

Update:

It's possible to make a few small modifications to the Button control through inheritance and still work fairly well. This solution eliminates the need to implement your own event handlers for OnCommand (although if you want to learn how to do that I can show you how that is handled). It also fixes the issue of submitting a value that has markup in it, except for IE probably. I'm still not sure how to fix IE's poor implementation of the Button tag though. That may just be a truly technical limitation that is impossible to work around...

[ParseChildren(false)]
[PersistChildren(true)]
public class ModernButton : Button
{
    protected override string TagName
    {
        get { return "button"; }
    }

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

    // Create a new implementation of the Text property which
    // will be ignored by the parent class, giving us the freedom
    // to use this property as we please.
    public new string Text
    {
        get { return ViewState["NewText"] as string; }
        set { ViewState["NewText"] = HttpUtility.HtmlDecode(value); }
    }

    protected override void OnPreRender(System.EventArgs e)
    {
        base.OnPreRender(e);
        // I wasn't sure what the best way to handle 'Text' would
        // be. Text is treated as another control which gets added
        // to the end of the button's control collection in this 
        //implementation
        LiteralControl lc = new LiteralControl(this.Text);
        Controls.Add(lc);

        // Add a value for base.Text for the parent class
        // If the following line is omitted, the 'value' 
        // attribute will be blank upon rendering
        base.Text = UniqueID;
    }

    protected override void RenderContents(HtmlTextWriter writer)
    {
        RenderChildren(writer);
    }
}

To use this control, you have a few options. One is to place controls directly into the ASP markup.

<uc:ModernButton runat="server" 
        ID="btnLogin" 
        OnClick="btnLogin_Click" 
        Text="Purplemonkeydishwasher">
    <img src="../someUrl/img.gif" alt="img" />
    <asp:Label ID="Label1" runat="server" Text="Login" />
</uc:ModernButton>

You can also add the controls to the control collection of the button in your code-behind.

// This code probably won't work too well "as is"
// since there is nothing being defined about these
// controls, but you get the idea.
btnLogin.Controls.Add(new Label());
btnLogin.Controls.Add(new Table());

I don't know how well a combination of both options works as I haven't tested that.

The only downside to this control right now is that I don't think it will remember your controls across PostBacks. I haven't tested this so it may already work, but I doubt it does. You'll need to add some ViewState management code for sub-controls to be handled across PostBacks I think, however this probably isn't an issue for you. Adding ViewState support shouldn't be terribly hard to do, although if needed that can easily be added.

Solution 4

You could make a new control, inheriting from Button, and override the render method, or use a .browser file to override all Buttons in the site, similar to the way the CSS Friendly stuff works for the TreeView control etc.

Solution 5

You can use the Button.UseSubmitBehavior property as discussed in this article about rendering an ASP.NET Button control as a button.


EDIT: Sorry.. that's what I get for skimming questions on my lunch break. Are there reasons why you wouldn't just use a <button runat="server"> tag or an HtmlButton?
Share:
84,247
Adam Lassek
Author by

Adam Lassek

Updated on July 08, 2022

Comments

  • Adam Lassek
    Adam Lassek almost 2 years

    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?

  • Adam Lassek
    Adam Lassek over 15 years
    I want a button control, not a hyperlink. Of course I know about LinkButton.
  • Adam Lassek
    Adam Lassek over 15 years
    It sounds like overriding the render method may be part of the solution I need. Have you done this yourself? Can you go into more detail?
  • Tsvetomir Tsonev
    Tsvetomir Tsonev over 15 years
    It can be styled to look just as one. Look here for example: hedgerwow.com/360/dhtml/…
  • Steven Robbins
    Steven Robbins over 15 years
    I have not done it for button, but I have for other controls. I'll stick some code up when I'm back on my PC if nobody beats me to it.
  • Adam Lassek
    Adam Lassek over 15 years
    Buttons are rendered by the operating system, and each one looks different. Within certain operating systems, they can also be themed by the user. If I themed the button to look like a Vista button, then it would look incongruent everywhere else.
  • Tsvetomir Tsonev
    Tsvetomir Tsonev over 15 years
    If that's what you need... I was thinking of completely restyling the buttons when you mentioned CSS and images. Anyways, just keep an eye on IE ;)
  • Adam Lassek
    Adam Lassek about 15 years
    You're halfway there, but a major reason to use the button tag is the ability to add markup inside it, such as an embedded image. That seems to be the hardest part, allowing content inside a server control. I haven't found a description of how to do that.
  • Dan Herbert
    Dan Herbert about 15 years
    That's very easy to do. I'll update my answer to demonstrate it.
  • Adam Lassek
    Adam Lassek about 15 years
    This has got me a lot closer to the solution, thanks. However, there's still some major things that need to be solved before this could work. See my amended question for details.
  • Adam Lassek
    Adam Lassek almost 15 years
    The name tag is not the problem. See asp.net/learn/whitepapers/request-validation It's because instead of posting the value attribute, like its supposed to, IE posts the markup from the tag instead, and posting back markup will always throw a server error in ASP.Net if you don't disable request validation.
  • Adam Lassek
    Adam Lassek almost 15 years
    The most significant hurdle to using the button tag is the fact that IE behaves so differently from everything else. You may have to change the post values in javascript before they're sent to the server in order for it to behave predictably.
  • David Andersson
    David Andersson almost 15 years
    @Adam Yes, you are correct, that is the problem. Do you now a workaround for that? Without using javascript.
  • David Andersson
    David Andersson almost 15 years
    I've come to the conclusion not to use the button tag at all, unfortunately. I like it but for my use there's a 50 % IE penetration.
  • Adam Lassek
    Adam Lassek almost 15 years
    Yes, that does work although it is not equivalent to asp:button, since you lose a lot of functionality. In my case the button is inside a databound control, and so it's of very limited utility without CommandName and CommandArgument.
  • G. Ghez
    G. Ghez over 12 years
    You can add custom attributes on such controls and retrieve values in code behind easily.
  • niico
    niico about 10 years
    This appears to break causesvalidation="false"
  • Daniel Liuzzi
    Daniel Liuzzi almost 9 years
    +1 This is the most complete answer as no built-in control addresses the question's requirements. See my answer below for a refactored version.
  • Daniel Liuzzi
    Daniel Liuzzi almost 9 years
    Please note that this solution —much like a LinkButton— will not work if JavaScript is disabled (source).
  • Martin Braun
    Martin Braun over 8 years
    The custom control still has a bug: AddParsedSubObject never gets called when the sub content contains any render expression. Example: <mc:ModernButton runat="server"><b><%= this.AnyString %></b></mc:ModernButton>. However, you can use resource ('<%$ Resources:AnyString %>') or binding expressions ('<%# this.AnyString %>') on the Text property directly, but this forces you to put the whole content in one expression (incl. HTML).
  • krlzlx
    krlzlx about 8 years
    @modiX: Great! That resolved my issue. I used the Text property to add some text and and image.
  • kman
    kman almost 8 years
    As far as I can tell, the custom control will always cause a full postback, even when inside an UpdatePanel.
  • Brack
    Brack about 6 years
    @niico My issue as well. Would love for native HTML5 button support in ASP.NET webforms.
  • J Stuart
    J Stuart almost 6 years
    This Custom Control almost worked correctly for me, but it didn't correctly allow the use of a nested control that had values assigned server-side (AddParsedSubObject ran before the nested control had its value altered - even if I moved the manipulation into Page_Init). In the end, I had to take Dan Herbert's OnPreRender & RenderContents methods. These allowed nested controls to be changed server-side. So far I'm using this successfully with CommandName & CommandArgument in a Repeater as well as the main submit button on a form. Thanks all!
  • mparkuk
    mparkuk over 5 years
    You may need to add the assembly also: <%@ Register TagPrefix="mc" Namespace="ModernControls" Assembly="myApplication" %>