Share via


What does runat="server" do for HTML controls

One thing I didn't fully understand when I first started using ASP.NET is exactly what runat="server" does. It sounds like some magical thing where code is running on the server for your control and not on the client. In some cases something like that does happen, but the basics are much simpler than that.

Let's take a look at the source code generated for a simple control using the trick I blogged about yesterday. We'll start with this simple page:

 <%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="https://www.w3.org/1999/xhtml" >
<head>
    <title>Untitled Page</title>
</head>
<body>
    <button>foo</button>
</body>
</html>

Here's a look at some of the source generated (I've simplified a bunch of the debugging info for clarity):

         private void @__BuildControlTree(default_aspx @__ctrl) {
            
            this.InitializeCulture();
            
            System.Web.UI.IParserAccessor @__parser = ((System.Web.UI.IParserAccessor)(@__ctrl));
            
            @__parser.AddParsedSubObject(new System.Web.UI.LiteralControl(
                        "\r\n\r\n<html>\r\n<head>\r\n    <title>Untitled</title>\r\n</head>\r\n<body>\r\n    <button>foo" +
                        "</button>\r\n</body>\r\n</html>\r\n"));
            
        }

Notice that the control tree simply has a LiteralControl with all of the HTML, including the button. Without runat="server", nothing special is done with it. But, if we add runat="server", we get:

         private global::System.Web.UI.HtmlControls.HtmlButton @__BuildControl__control2() {
            global::System.Web.UI.HtmlControls.HtmlButton @__ctrl;
            
            @__ctrl = new global::System.Web.UI.HtmlControls.HtmlButton();
            
            System.Web.UI.IParserAccessor @__parser = ((System.Web.UI.IParserAccessor)(@__ctrl));
            
            @__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("foo"));
            
            return @__ctrl;
        }
        
        private void @__BuildControlTree(default_aspx @__ctrl) {
            
            this.InitializeCulture();
            
            System.Web.UI.IParserAccessor @__parser = ((System.Web.UI.IParserAccessor)(@__ctrl));
            
            @__parser.AddParsedSubObject(new System.Web.UI.LiteralControl(
                  "\r\n\r\n<html>\r\n<head>\r\n    <title>Untitled</title>\r\n</head>\r\n<body>\r\n    "));
            
            global::System.Web.UI.HtmlControls.HtmlButton @__ctrl1;
            
            @__ctrl1 = this.@__BuildControl__control2();
            
            @__parser.AddParsedSubObject(@__ctrl1);
            
            @__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n</body>\r\n</html>\r\n"));
        }

Now BuildControlTree adds a literal control for the stuff before the button, adds an HtmlButton for the button itself (with the text inside the control as a literal), and then another LiteralControl for the rest HTML on the page. What happens is that when the control tree is rendered, the HtmlButton will render itself with a <button> tag. In this case, you'll end up with the exact same HTML in the browser as our first example, but let's see what else we can do.

One thing you might notice when you set runat="server" is that the set of properties available in the Properties Window changes. When you don't have runat="server", what you are looking at is the standard <button> properties. Once you turn on runat="server" what you are looking at is the properties on HtmlButton (which are generally a superset of the HTML properties). For example, if you try adding visible="false" to the button without runat="server", the visible="false'" is just rendered as part of the LiteralControl for the HTML and the browser ignores it because that's not an HTML property on buttons. But, if you do so with runat="server", you'll see in the server side .cs source code that the HtmlButton's visible property is set to false. When the page is rendered by the server, the HtmlButton doesn't render any code for the button and you don't end up with any HTML for the button on the browser.

The other thing we can do is get programmatic access to the button in our code behind. To do so, we need to add an id to the button. If we switch the button to:

     <button runat="server" id="_button">foo</button>

Then, the generated source code will include:

     protected global::System.Web.UI.HtmlControls.HtmlButton _button;

Now, we could add the following to our Page_Load:

     protected void Page_Load(object sender, EventArgs e)
    {
        _button.InnerText = "bar";
    }

After the control tree is built, it will get into Page_Load and we'll change the InnerText of the button. When the server renders the control tree, it will output "bar" for the text of the button instead of "foo" and the resulting HTML will have <button id="_button">bar</button>.

Something interesting to note is that this works for any kind of HTML tag. In some cases ASP.NET has richer classes backing the HTML tags (like HtmlButton). But, if you just put something like <foo runat="server">, it will go through the same process, but give you an HtmlGenericControl.

One thing you can't do with the <button> is add a click handler that runs code on the server. To do that, you'll need the asp:button Web Server control. Those are somewhat different beasts than HTML controls. I'll discuss those in a future post.

Comments

  • Anonymous
    December 17, 2006
    You can add a click handler, either in markup using the OnServerClick attribute, or in code using the ServerClick event. In either case the resulting HTML will have an onclick="__doPostBack('button','')" attribute. Double-clicking the button in the Designer in Visual Studio will automatically add a click handler, just like it does for asp:Button. The seame also applies to HtmlAnchor, HtmlInputButton and HtmlInputImage.

  • Anonymous
    December 17, 2006
    You are right! It was selecting the text of the button for me when double-clicking. If you don't put the button in a <form>, then __doPostBack is undefined. It's interesting that the HTML control requires javascript, but the Web Server control doesn't. Thanks for the clarification!

  • Anonymous
    December 17, 2006
    Javascript is required for onserverclick because HtmlButton and HtmlInputButton with Type="Button" are just push buttons without default behavior. An HtmlInputButton with Type="Submit" doesn't require javascript for onserverclick. The asp:Button by default renders also renders an HTML input element with type="submit". There are however several things that can cause asp:Button to use onclick javascript as well:

  • If you specify UseSubmitBehavior="false", which causes it to render an HTML input element with type="button"
  • If you specify a PostBackUrl
  • If CausesValidation="true" (default) and validation is used in the form
  • Anonymous
    December 17, 2006
    Oh, and one more thing regarding the double-clicking. You have to make sure that you first select the button itself and are not editing it's content (which can for example include text and/or an image). With the HtmlInputButton and asp:Button this problem cannot happen, because Visual Studio does not allow you do edit the label text in the Designer, you need to use the Properties window for that. Also, the HtmlControls are convenient if you want to quickly add some programmatic control to existing HTML, but in general it is recommended to use the WebControls, because they are more feature rich and resemble Windows Forms controls. But I'm sure you'll mention this in your future post. :-)

  • Anonymous
    January 05, 2007
    The comment has been removed

  • Anonymous
    January 29, 2007
    I'm not sure I totally understand the question. Why not use a master page instead of a control?