TRULY Understanding Dynamic Controls (Part 1)
Part 1: Dynamic vs. Static
Part 2: Creating Dynamic Controls
Part 3: Adding Dynamic Controls to the Control Tree
Part 4: Because you don't know to render at design time
Just like ViewState, dynamic controls seem to be fodder for much debate, and a source of many confusing issues. This article will be the first of a multi-part series to detail just about everything you could ever want to know about how Dynamic Controls fit in with the framework.
ASP.NET has been out for half a decade. Maybe this article is a little late in the making. But with all of the new technologies coming down the pipe (e.g., [Atlas]), I thought it would be nice to get back to the basics. After all, not all of us have been in the game since the beginning :)
Actually, this article should probably be titled "TRULY Understanding ASP.NET". Because as you will see, dynamic controls really aren't that special. In learning how they fit in with the page life cycle, and how they are similar to static controls, you will gain an understanding of how ASP.NET works in general.
I decided to write this article as a direct consequence of the article I wrote on ViewState. Over the course of a few months I received dozens upon dozens of comments on that article from developers needing help. They assumed the trouble they were having was a ViewState related issue. But 75% of the time, it was actually a dynamic control issue!
Part of the problem is that so many developers think they need to create controls dynamically when there's often much more elegant or easier solutions. There's a tendency for developers to try and do "too much" themselves, and that leads to complex problems that seem to have no good solution. It's frustrating and discouraging. I've been there myself -- but I've learned that, almost always, when I find myself in that situation it's because I'm failing to see a bigger picture or I'm failing to approach the problem in an "asp.net way". I hope this article helps those who are experiencing the same thing.
I also decided to break this article up into multiple episodes, because the ViewState article is quite long, and I think this article is going to be even longer. Maybe if I give it to you in more bite size pieces it will be easier to read, no?
PART 1
Dynamic vs. Static: What's the difference?
Normally you "declare" controls on a form via markup and the ubiquitous runat="server" attribute. Like this:
<%@ Page Language="C#" AutoEventWireup="true"
CodeFile="MyPage.aspx.cs" Inherits="Infinity.Examples.MyPage" %>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>TRULY Understanding Dynamic Controls</title>
</head>
<body>
<form id="form1" runat="server">
Your name: <asp:TextBox ID="txtName" runat="server" Text="Enter your name" />
</form>
</body>
</html>
That TextBox is a "static" control. Static controls are declared in an xml-like syntax in ASPX or ASCX files. When ASP.NET parses that markup, it generates a class on the fly that does the dirty work for you -- one by one, the code creates the controls and adds them to the control tree. The TextBox declared above results in the following auto-generated code:
private global::System.Web.UI.WebControls.TextBox @__BuildControltxtName() {
global::System.Web.UI.WebControls.TextBox @__ctrl;
#line 11 "c:\projects\Truly\MyPage.aspx"
@__ctrl = new global::System.Web.UI.WebControls.TextBox();
#line default
#line hidden
this.txtName = @__ctrl;
@__ctrl.ApplyStyleSheetSkin(this);
#line 11 "c:\projects\Truly\MyPage.aspx"
@__ctrl.ID = "txtName";
#line default
#line hidden
#line 11 "c:\projects\Truly\MyPage.aspx"
@__ctrl.Text = "Enter your name";
#line default
#line hidden
return @__ctrl;
}
The code may look scary, but it's just a really careful way of creating a TextBox, setting the ID and Text properties, and returning it. It has to be careful because it's possible that user code define things that make change the intended purpose. The #line pragmas are so the compiler can alert you to any compile errors and still give you the correct line number in the declared markup, rather than the line number of the generated code (say thank you to the compiler next time that happens -- its a great feature).
There will be one of these "build control" methods for each control you statically declare on the page. If a control exists inside another control, then the parent control's "build control" method will add them to it's own control collection. In this example, the form control contains three child controls:
private global::System.Web.UI.HtmlControls.HtmlForm @__BuildControlform1() {
global::System.Web.UI.HtmlControls.HtmlForm @__ctrl;
#line 10 "c:\projects\Truly\MyPage.aspx"
@__ctrl = new global::System.Web.UI.HtmlControls.HtmlForm();
#line default
#line hidden
this.form1 = @__ctrl;
#line 10 "c:\projects\Truly\MyPage.aspx"
@__ctrl.ID = "form1";
#line default
#line hidden
System.Web.UI.IParserAccessor @__parser = ((System.Web.UI.IParserAccessor)(@__ctrl));
#line 10 "c:\projects\Truly\MyPage.aspx"
@__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n Your name: "));
#line default
#line hidden
global::System.Web.UI.WebControls.TextBox @__ctrl1;
#line 10 "c:\projects\Truly\MyPage.aspx"
@__ctrl1 = this.@__BuildControltxtName();
#line default
#line hidden
#line 10 "c:\projects\Truly\MyPage.aspx"
@__parser.AddParsedSubObject(@__ctrl1);
#line default
#line hidden
#line 10 "c:\projects\Truly\MyPage.aspx"
@__parser.AddParsedSubObject(new System.Web.UI.LiteralControl("\r\n "));
#line default
#line hidden
return @__ctrl;
}
The child controls are:
- A LiteralControl containing the straight-up text "Your name:"
- The txtName TextBox
- Another LiteralControl containing the whitespace before the end of the form tag.
Notice to create the TextBox control, this method calls that control's "build method" that we just looked at:
global::System.Web.UI.WebControls.TextBox @__ctrl1;
#line 10 "c:\projects\Truly\MyPage.aspx"
@__ctrl1 = this.@__BuildControltxtName.@__BuildControltxtName();
And notice this strange method it calls, passing the TextBox:
@__parser.AddParsedSubObject(@__ctrl1);
Because of the fact the "object" being added is a control, all that method does is call Controls.Add(@__ctrl1), thus adding the TextBox to the control tree (I say "because of the fact it is a control", because non-controls can be added as well, but how they are handled is unique to each control type).
This isn't shown in this example, but haven't you ever wondered why you declare control variables as protected? Why not private? The class that is auto-generated inherits from your code-behind (that's why you need an "inherits" attribute in the page directive). It's up to this code to create the declared controls and assign them to the variables you declare (if it exists) so that you can easily access them. Since it is derived from your code-behind class, if the variable were private, it couldn't do that!
So that is how the framework adds declared controls into the control tree. A dynamic control in the traditional sense is one that you as the page or control developer create at some point during the page lifecycle, and add to the static control tree yourself. Something like this:
protected override void OnInit(EventArgs e) {
TextBox txtName = new TextBox();
txtName.ID = "txtName";
txtName.Text = "Enter your name";
this.form1.Controls.Add(txtName);
base.OnInit(e);
}
This code is not nearly as scary looking as the auto-generated code from the page parser. However, it does the same thing!
So you see, this whole silly ASPX-markup-runat-server-thing exists solely to make it easier on you to create a control tree! That's all it's there for! If it didn't exist, you could still achieve all the same functionality by creating the controls and setting their properties yourself, just like the auto-generated code does! When I first realized this, it was a eureka moment for me. So if you don't feel the same way, either you don't understand what I'm saying, or I'm crazy when compared to you. Maybe it's a little of both :)
This really blurs the line between static and dynamic controls, as it should. What you think of as a static control, is really just a control that is being dynamically created by the framework instead of by you. But neither the control or the framework really care who creates it -- they can participate in the page lifecycle one and the same, and both the control and the page it sits on don't even know the difference.
But wait!
If you have ever dealt with dynamic controls before, you know that they really are different. It's true that your experience with them may differ from that of static controls, but it's important to understand exactly why there's a difference. The difference has to do with when the control enters the control tree. When the framework adds controls, it does it extremely early in the event sequence. The only thing that happens first is the control or page constructor. That's why in OnInit or OnPreInit, the controls already exist and are ready for use (well, master pages can 'mess' with that process, but this is still a true statement).
But when you dynamically create a control, you don't have the ability to do it as early as the framework does. And that has consequences...
Stay tuned. :)