Introduction
When coordinating code between a master page, a child page and several controls, it can be very useful to have a listing of when each event is fired. There are a lot of lists on the web but they are rarely complete, and I have yet to find the code on how the lists were created. In the end, I wrote my own testing framework, which is the topic of this article.
It turns out to be very simple: override methods to make a note of what is happening, then continue. I did this by creating custom classes to replace the standard Page
and MasterPage
objects, and by writing "wrapper" web controls. If you are familiar with these techniques or are just looking for a reference, feel free to go to the Results sections at the bottom.
This article is targeted towards beginner-ish web programmers who already know the basics of web programming and have a test site where they can experiment. It was written and tested using ASP.NET 2.0, but there's nothing here that should break with any later versions of .NET. I used VB because that is the language I use every day; the code is simple and should translate into C# very easily.
TestChild and TestMaster
The first thing I did was create the TestChild
and TestMaster
classes, which will replace the regular Page
and MasterPage
classes. The advantage to putting the code in separate classes is that we can have several different test pages without having to copy the code. They start off looking like this:
Public Class TestChild
Inherits System.Web.UI.Page
End Class
Public Class TestMaster
Inherits System.Web.UI.MasterPage
End Class
Next is to override the methods we want to document. All of these overrides look the same: they write some text into the response stream, then call the same method in the base class. Here is a typical example:
Protected Overrides Sub OnLoad(ByVal e As System.EventArgs)
Response.Write("Child Load<br/>" + vbCrLf)
MyBase.OnLoad(e)
End Sub
The break tag will put the text on its own line when the page is delivered to the browser, and the carriage return/line feed will put a break in the page's source. These just make the text easier to read. The OnLoad
override in TestMaster
is identical, except that it writes "Master Load".
In TestChild
, I overrode these methods:
LoadControlState
LoadViewState
OnDataBinding
OnInit
OnInitComplete
OnLoad
OnLoadComplete
OnPreInit
OnPreLoad
OnPreRender
OnPreRenderComplete
OnSaveStateComplete
Render
RenderChildren
RenderControl
SaveControlState
SaveViewState
Master pages do not have as many interesting methods. Here is what I overrode in TestMaster
:
LoadControlState
LoadViewState
OnDataBinding
OnInit
OnLoad
OnPreRender
Render
RenderChildren
RenderControl
SaveControlState
SaveViewState
You may have noticed that I did not override the OnUnload
method. This is because the Unload
event is fired after the page has been rendered; Response
no longer exists so an error gets thrown.
TestButton and TestGrid
To get information on control events, we need to create custom controls that will report back. I chose to write a button and a grid, because the ordering of Click
and DataBinding
is usually where I go wrong (I'm not the only one to change a grid's data before the data is reloaded, am I?)
Namespace TestControls
Public Class TestButton
Inherits System.Web.UI.WebControls.Button
Protected Function Response() As HttpResponse
Return HttpContext.Current.Response
End Function
End Class
Public Class TestGrid
Inherits System.Web.UI.WebControls.GridView
Protected Function Response() As HttpResponse
Return HttpContext.Current.Response
End Function
End Class
End Namespace
Web controls do not have a Response
method so I added one; this is just to make the code a bit cleaner and easier to maintain.
By putting the controls in a namespace, we can configure the website to attach a prefix for IntelliSense. I'm not sure if this is actually necessary, but it is useful enough that I always do it. In the web.config
file, find the <pages>
block inside <system.web>
, and add this:
<controls>
<add tagPrefix="test" namespace="TestControls"/>
</controls>
As with TestChild
and TestMaster
, we override the methods we want to document so they write to the response stream and call the underlying method. The only difference is that we use the ID
property of the control rather than static text; this way, we can have several controls on a page and know which message belongs to which control.
Protected Overrides Sub OnPreRender(ByVal e As System.EventArgs)
Response.Write(Me.ID + " PreRender<br/>" + vbCrLf)
MyBase.OnPreRender(e)
End Sub
Most of the interesting methods I wanted to document were in both controls:
LoadControlState
LoadViewState
OnDataBinding
OnInit
OnLoad
OnPreRender
Render
RenderChildren
RenderContents
RenderControl
SaveControlState
SaveViewState
In TestButton
, I also overrode OnClick
and OnCommand
. In TestGrid
, I overrode OnDataBound
.
For the grid, let's create a very simple XML data file that we can link to.
="1.0"="utf-8"
<People>
<Person firstName="Gregory" lastName="Gadow"/>
</People>
Test.master and Default.aspx
Now we are ready to actually create some pages. For this demo, I am using a single master page and a single child page.
<%@ Master Language="VB" Inherits="TestMaster"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title></title>
</head>
<body>
<form id="form1" runat="server">
<asp:ContentPlaceHolder id="ContentPlaceHolder1" runat="server" />
</form>
</body>
</html>
<%@ Page Language="VB" Inherits="TestChild"
MasterPageFile="~/Test.master" Title="Untitled Page" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
</asp:Content>
The key here is the Inherits
attribute in the <%@ Master %>
and <%@ Page %>
directives. If you are using the single-page code model (which is what I'm doing here), Inherits
tells the compiler to use a class other than the standard one as the page's base; you will need to add the tag yourself. If you are using separate code-behind files, the tag is inserted automatically and will point to your code-behind; you do NOT want to change it there. Instead, go to the code-behind file and change it to be based on your custom classes.
Results, Test 1 - Master and Child
We are ready for our first test. Open a browser and point it to the child page you just created.
Child PreInit
Master Init
Child Init
Child InitComplete
Child PreLoad
Child Load
Master Load
Child LoadComplete
Child PreRender
Master PreRender
Child PreRenderComplete
Child SaveViewState
Master SaveViewState
Child SaveStateComplete
Child RenderControl
Child Render
Child RenderChildren
Master RenderControl
Master Render
Master RenderChildren
There you are: the events of a master and child page, documented in the order they occurred. If you look at the browser source, you will notice that the text occurs before even the DOCTYPE
declaration. This makes sense, because the messages were sent to the response stream before the page was rendered.
Results, Test 2a - Controls, First Load
Now we add some controls to the child page:
<%@ Page Language="VB" Inherits="TestChild" MasterPageFile="~/Test.master"
Title="Untitled Page" %>
<asp:Content ID="Content1" ContentPlaceHolderID="ContentPlaceHolder1" Runat="Server">
<hr />
<h3>Child page controls</h3>
<div style="margin-bottom:1em;">
<test:TestGrid ID="Child_Grid" runat="server" DataSourceID="TestData" />
<asp:XmlDataSource ID="TestData" runat="server"
DataFile="~/App_Data/TestData.xml"/>
</div>
<test:TestButton ID="Child_Button" runat="server" />
</asp:Content>
The horizontal rule is just a visual separator from the text and our controls, and the div
tag groups the grid and its data source while providing a bit of spacing. The documentation now shows:
Child PreInit
Child_Grid Init
Child_Button Init
Master Init
Child Init
Child InitComplete
Child PreLoad
Child Load
Master Load
Child_Grid Load
Child_Button Load
Child LoadComplete
Child PreRender
Master PreRender
Child_Grid DataBinding
Child_Grid DataBound
Child_Grid PreRender
Child_Button PreRender
Child PreRenderComplete
Child_Grid SaveControlState
Child SaveViewState
Master SaveViewState
Child_Grid SaveViewState
Child_Button SaveViewState
Child SaveStateComplete
Child RenderControl
Child Render
Child RenderChildren
Master RenderControl
Master Render
Master RenderChildren
The events for Child_Grid
occur before Child_Button
because the grid is above the button. If you reverse these controls, you will see that the order of their events also gets reversed.
There are a few interesting things to note. The grid's DataBinding
and DataBound
events occur after the master page's PreRender
but before the child page's PreRender
. The grid saves its control state, but the button does not. And the controls save their view state after the master page does, which saves its view state after the child page.
Below the line, we see how the controls got rendered. Notice that the grid gets put on the page during RenderChildren
while the button is written during Render
.
Results, Test 2a - Controls, PostBack
Click on the button to create a postback, and look at the events.
Child PreInit
Child_Grid Init
Child_Button Init
Master Init
Child Init
Child InitComplete
Child_Grid LoadControlState
Child_Grid LoadViewState
Child_Button LoadViewState
Child PreLoad
Child Load
Master Load
Child_Grid Load
Child_Button Load
Child_Button Click
Child_Button Command
Child LoadComplete
Child PreRender
Master PreRender
Child_Grid PreRender
Child_Button PreRender
Child PreRenderComplete
Child_Grid SaveControlState
Child SaveViewState
Master SaveViewState
Child_Grid SaveViewState
Child_Button SaveViewState
Child SaveStateComplete
Child RenderControl
Child Render
Child RenderChildren
Master RenderControl
Master Render
Master RenderChildren
The grid does not bind to any data this time; instead, it reloads the data from the control state. Because this is a postback, there is a view state that can be loaded so the controls do just that. After the button gets loaded, its Click
event is processed, then its Command
event. The controls are rendered just as they were before.
Moving On
There are many more tests that can be done: put controls on the master page above and below the content placeholder, try nested master pages, track different controls. Please experiment, and if you get any interesting results, please post them as comments to this article.
History
- Version 2 - September 2, 2010 - Initial release
Gregory Gadow recently graduated from Central Washington University with a B.S. that combined economics and statistical analysis, and currently works for the Washington Department of Fish & Wildlife as an IT developer. He has been writing code for 30 years in more than a dozen programming languages, including Visual Basic, VB.Net, C++, C#, ASP, HTML, XML, SQL, and R.