The task of creating dynamic ASP.NET Web Forms whose behavior is based upon user interaction and depends upon the purpose and intended goal of the Web Form.

Web Forms that require only controls and functionality provided by the built-in ASP.NET Web server controls are easy to create. But creating Web Forms that require or are designed with extended controls and functionality can be a challenge.

In 2000, I worked on a client database in SQL Server that included 134 tables. I designed the application so that the client could use stored procedures to access all data. In my design, each table had a minimum of four stored procedures for working with its data. Such stored procedures common to each table are referred to as either the NURD (New, Update, Read, Delete) stored procedures for the table or the CRUD (Create, Read, Update, Delete) stored procedures for the table depending on who you ask.

Many times the code contained in these stored procedures would be extremely repetitious with the only difference between the New/Create stored procedures for each table being table and column names while the rest of the code remained the same. This also applies to the other types of stored procedures for each table. Each table could then have additional stored procedures beyond the NURD stored procedures. Since there is so much repetition between the types of stored procedures from table to table, the task of creating these stored procedures turns into a lengthy period of copying, pasting, modifying, and saving code several hundred times.

The development team I worked with estimated that we would spend approximately 90 hours to create the NURD stored procedures. We dreaded this mundane task. As a solution, my team created an automated stored procedure builder using classic ASP and SQL Distributed Management Objects (SQL-DMO). I'll outline how this tool was created in a future article.

In 2001 (.NET beta 2), I migrated this automated stored procedure builder from classic ASP to ASP.NET and consolidated it from a series of classic ASP Web pages to a single ASP.NET Web Form. Although I was working on other ASP.NET projects as well, through this migration I learned some important things about ASP.NET. The most significant thing I learned related to the event life cycle of ASP.NET Web Forms.

An Example Scenario

The migration project mentioned above requires too much explanation and would take this article down a different road than intended, so I'll use a sample Web Form to illustrate the principles that I will teach you.

My sample Web Form simply lists some company types in a drop down list box. Once you select a type of company, the form lists all of the companies for the selected type. Although the migration project above employed custom button type controls, this sample application uses built-in Web server buttons that you can click to display details for a company from those listed, as shown in Figure 1.

Figure 1: The sample application listing two accounting companies.
Figure 1: The sample application listing two accounting companies.

The View Details buttons are displayed next to each company and are wired into a function member that identifies the company and then displays details for it. Each button for displaying details does not exist prior to adding it to the page**?**my application creates each button on the fly and then places it on the page as the page gets generated. In the code below you can see the C# code used to generate a button on the fly.

// Create a new instance of a button.
Button companyDetailsButton;
companyDetailsButton = new Button();
companyDetailsButton.CausesValidation = true;
companyDetailsButton.CssClass = "btnstandard";
companyDetailsButton.ID = _
   "btnAccountingDetailsGWAS";
companyDetailsButton.Text = "View Details";

This code creates a Web server button control named companyDetailsButton on the fly and configures some of its properties. This button must then be added to some type of container control in order to actually get it on the page. It could be added directly to the ASP.NET Web Form with code such as the following:

// Add the button to the Web Form.
Page.Controls.Add(companyDetailsButton);

Code like this presents a problem because the button is a direct child control of the Web Form**.** There is no way to determine where the code to display the button is inserted into the code for the Web Form and so it is written at the end of the HTML stream or just after the closing </html> tag. This results in an HTML page that is not well formed.

However, because Internet Explorer is so forgiving, it displays the button just fine. The problem is that the added control is an ASP.NET Web server control that must reside inside of a server-side form element and, thus, the ASP.NET engine does not allow the page to be displayed by the browser, as shown in Figure 2. In order to work around this limitation, I add controls to a container that resides within a server-side form element, such as a table or placeholder.

Figure 2: The result of adding a control directly to a Web Form.
Figure 2: The result of adding a control directly to a Web Form.

Microsoft did a good job of normalizing the object model for working with controls in code so that the process for adding a control to any container control is consistent. The following code adds the companyDetailsButton to a table data cell called companyDetails.

// Add the button to a table data cell.
companyDetails.Controls.Add(DetailsButton);

The code above illustrates dynamically creating the button, but the button does not yet take any actions. In order to actually get the button to take an action when a user clicks it, I had to add an event handler to the button as follows.

// Add event handler to Click event.
companyDetailsButton.Click += new EventHandler(this.ShowDetailAccounting);

This code adds a new event handler of the type System.EventHandler to the Click event of the companyDetailsButton button. The event handler expects to receive the name of a function member to execute when someone clicks the button. In this case, the event handler is passed the name of the ShowDetailAccounting function member that resides in the code behind for the Web Form. It may seem as though this code should work just fine. However, although you can place this code anywhere in the execution process, it will not take effect unless you place it in the correct location.

The Page Event Life Cycle

This is where a thorough understanding of the page event life cycle comes into play. ASP.NET differs from classic ASP in many ways, one of which is the way in which client-side interaction is handled once you submit it back to the server. In classic ASP, the developer handled this process, which meant that the server handled each page in a unique manner. ASP.NET changed this by imposing a structured event life cycle that introduced a uniform series of function members (methods) that are executed consistently each and every time that it renders a Web Form. The function members that handle events are listed and described in Table 1 in the order in which they are processed.

By far, the most commonly used function member listed in Table 1 is the OnLoad() function member. In fact, when you create any type of ASP.NET Web item such as a Web Form or a Web user control, Visual Studio .NET automatically adds this function member to the code behind class for you, whereas you must manually add (override) most of the other function members. In this function member, the current values of the controls on the Web Form are available and code may interact directly with them. Let me state a few very important observations in relation to dynamically created controls.

You can dynamically create controls and wire events into them at any point in the execution process. However, you can only wire events into a control in either the OnInit() function member or in the OnLoad() function member. If the events are encountered at any other point in the cycle of events, it will not cause an exception to be thrown but it will have no affect on the control. The code snippet below shows an example of wiring an event into a control in the OnInit() function.

// Override the OnInit() method.
override protected void OnInit(EventArgs e)
{

  // Dynamically create controls that do not
  // depend upon data on the Web Form.
  Button companyDetailsButton = new Button();
  companyDetailsButton.Click += new EventHandler(this.ShowDetailAccounting);
{

Levels of Events

ASP.NET implements three levels of events: validation, cached, and PostBack

Validation events are handled by validation controls on the client's machine at the page level via snippets of JScript embedded in the page. Validation events will either return a true or false value based upon the success of the validation process. If validation fails, the controls will not allow the page to be posted back to the server.

Depending upon the type of control you use, each ASP.NET Web server control may raise events based upon user interaction. For instance, a DropDownListbox control will raise a SelectedIndexChanged event when the user selects a different option from the DropDownListbox. If the control has its AutoPostBack property set to true, raising certain events will cause the page to be posted back to the server. By default, only the Button, LinkButton, and ImageButton controls automatically cause the page to be posted back to the server. When controls raise events that do not cause the page to be posted back to the server, a note that the event was raised is cached in the ViewState of the page. Cached events are handled in the postback process just after the OnLoad() function member has completed executing.

Figure 3: The CompanyInformation Web Form with dynamically created and wired buttons in the OnLoad() method.
Figure 3: The CompanyInformation Web Form with dynamically created and wired buttons in the OnLoad() method.

The PostBack event is raised by any control that has its AutoPostBack property set to true. The event raised by the control that caused the postback to occur is handled in the postback process just after the cached events have been processed. The PostBack event is the final event processed in the page event life cycle.

A Limitation to Dynamic Creation

You should know about one important limitation in the sequence that ASP.NET uses to process events that directly impacts my technique for dynamically creating controls. You cannot dynamically create a control and wire an event into it in a cached event or in the PostBack event. ASP.NET processes cached events and the PostBack event after the OnLoad() function member and OnInit() function member have completed processing.

Conclusion

Many complex Web applications require forms to be created and rendered dynamically. The ability to dynamically add controls and content to forms has been an intrinsic feature of both classic ASP and ASP.NET. Microsoft ASP.NET vastly expands the process. Expertise in the dynamic creation process requires a thorough understanding of the ASP.NET page event life cycle.

You should not add a control directly to a Web Form because the code generated for the control will be inserted at the end of the HTML stream or just after the </html> closing tag.

You can only wire events into controls in either the OnInit() function member or the OnLoad() function member; if the control is dependant upon data on the Web Form, then the OnLoad() method is the only option.

You cannot dynamically create a control and wire an event into it in a cached event or in the PostBack event. Cached events and the PostBack event get processed after the OnLoad() function member and OnInit() function member have completed processing.

Listing 1: Dynamically creating and wiring button controls in the OnLoad() method (excerpted)


// Declare object variables.
HtmlTableRow companyRow;
HtmlTableCell companyId;
HtmlTableCell companyName;
HtmlTableCell companyDetails;
Button companyDetailsButton;
      
// Accounting Companies.

// Declare the items necessary to build the new
// rows of companies.
companyRow = new HtmlTableRow();
companyId = new HtmlTableCell();
companyName = new HtmlTableCell();
companyDetails = new HtmlTableCell();
companyDetailsButton = new Button();

// Build the details button.
companyDetailsButton.CausesValidation = true;
companyDetailsButton.CssClass = "btnstandard";
companyDetailsButton.ID = "btnAccountingDetailsGWAS";
companyDetailsButton.Text = "View Details";
companyDetailsButton.ToolTip = "Click here to view details for this company.";
companyDetailsButton.Click += new EventHandler(this.ShowDetailAccounting);

// Add the button to the cell.
companyDetails.Controls.Add(companyDetailsButton);
companyDetails.Style["text-align"] = "right;";

// Build the table data cells.
companyId.InnerText = "1";
companyName.InnerText = "Golden West Accounting Systems";
            
// Add the cells to the row.
companyRow.Cells.Add(companyId);
companyRow.Cells.Add(companyName);
companyRow.Cells.Add(companyDetails);
companyRow.Style["font-family"] = "Verdana, Geneva, Arial, Helvetica, sans-serif";
companyRow.Style["font-size"] = "8pt;";
companyRow.Style["background-color"] = "#eee8aa;";

// Add the row to the table.
tblCompanyInformation.Rows.Add(companyRow);

Table 1: The event function members of the page event life cycle in the order that they are processed.

Function MemberDescription
OnInit()Handles the Init event and initializes the Web Form and all of the controls contained on it.
LoadViewState()The ViewState property of all of the server controls are updated to reflect the state of the controls being received from the client. ASP.NET uses the ViewState setting of each control to determine which control values have been modified.
LoadPostData()Process incoming form data.
OnLoad()This is the most commonly used function member in the entire cycle. Controls are created, initialized, and the state is restored.
RaisePostDataChangedEvent()When the state of a control changes, events for each control are fired in response to the data change. These events are fired here.
RaisePostBackEvent()The event that caused the postback is fired.
OnPreRender()This is the last opportunity to interact with controls and form data prior to it being rendered and sent to the client.
SaveViewState()The state of all controls is saved to the encrypted ViewState string that is stored in the hidden control and persisted to the client.
Render()The output of the Web Form is rendered to be sent to the client.
Dispose()Final cleanup opportunity.
OnUnLoad()All server-side references to controls and the page itself are unloaded from memory.