In conventional ASP programming, developers typically access the Request object to get the parameters needed to render the page and render the content of the page through either the Response object or code rendering blocks. We also use other ASP objects such as the Application, Session, and Server objects to manage application variables, session variables, server settings, and so on.
As mentioned earlier, ASP.NET is intended to change all this spaghetti madness by introducing a much cleaner approach to server-side scripting framework: Web Forms, or programmable pages, and server controls.
In the following sections, we cover the components of a Web Form, its life cycles, the server controls that the Web Form contains, event handing for these server controls, as well as how to create your own server controls.
Similar
to VB Forms, a Web Form consists of two
components: the form with its controls and the code behind it that
handles events associated with the form’s controls. A Web Form
has the file extension
.aspx
and contains HTML
elements, as well as server controls. The code behind the form is
usually located in a separate class file. Note that while it is
possible to have both the form and the code in one file, it is better
to have separate files. This separation of user interface and
application code helps improve the spaghetti-code symptom that most
ASP-based applications are plagued with.
ASP.NET provides the Page class in the System.Web.UI namespace. This
class encapsulates all common properties and methods associated with
web pages. The code behind the class derives from this Page class to
provide extensions specific to the page we’re implementing. The
aspx
file provides the form layout and control
declarations. Figure 7-4 illustrates the
relationship between the Page base class, the Web Form code behind
the class, and the Web Form
user interface (UI).
As a Web Form developer, you will have to provide the latter two. The
Web Form UI is where you declare server controls with appropriate
IDs. The code behind the class is where you programmatically access
server controls declared in the Web Form UI, as well as handle events
from these controls. The following simple example shows the
aspx
page, the code behind source file, and how
they work together. The aspx
file
(TestEvent.aspx
) contains only HTML tags and a
directive that links to the code behind:
<%@ Page language="c#" codebehind="TestEvents.cs" inherits="CTestEvents" %>
<html>
<head><title>Testing Page Events with codebehind</title></head>
<body>
<form runat=server>
Init Time: <asp:Label id=labelInit runat=server/><br/>
Load Time: <asp:Label id=labelLoad runat=server/><br/>
<input type=submit />
</form>
</body>
</html>
The code-behind, TestEvent.cs
, contains the
class CTestEvents to which the aspx page is referring:
using System; public class CTestEvents : System.Web.UI.Page { protected System.Web.UI.WebControls.Label labelInit; protected System.Web.UI.WebControls.Label labelLoad; public CTestEvents( ) { labelInit = new System.Web.UI.WebControls.Label( ); labelLoad = new System.Web.UI.WebControls.Label( ); } public void Page_Init(Object oSender, EventArgs oEvent) { labelInit.Text = DateTime.Now.ToString( ); } public void Page_Load(Object oSender, EventArgs oEvent) { labelLoad.Text = DateTime.Now.ToString( ); if(IsPostBack) { labelLoad.Text += "(PostBack)"; } } }
You must compile TestEvent.cs
and place the DLL
in the /bin
directory under your web
application’s virtual directory before trying to access the
aspx page.[44] The command to compile this C# file is:
csc /t:library TestEvents.cs
ASP.NET parses the Web Form files to generate a tree of scriptable objects, where the root is the Page-derived object representing the current Web Form. This is similar to how the IE browser parses the HTML file and generates a tree of scriptable objects to be used in DHTML; however, the tree of objects for the Web Form files resides on the server side.
As you are already aware from our survey of the System.Web.UI namespace, the Page class actually derives from the Control class. In a sense, a Web Form is a hierarchy of Control-derived objects. These objects establish the parent-child relationship through the Parent and Controls properties.
Besides the Controls and Parent properties, the Page class also provides other useful properties, which are familiar to ASP developers—such as the Request, Response, Application, Session, and Server properties.
Because the Web Form is nothing but a programmable page object, using this object-oriented model is much more intuitive and cleaner than the conventional ASP development. As opposed to the linear execution of server-side scripts on an ASP page, ASP.NET enables an event-based object-oriented programming model.
Let’s take an example of a web page that contains a form with numerous fields. One or more of these fields display list information from a database. Naturally, we have code in the ASP page to populate these fields so that when a user requests this ASP page, the generated page would have the content ready. As soon as the last line of data is written to the browser, the ASP page is done. This means that if there were errors when the user submits the form, we will have to repopulate all the database-driven form fields, as well as programmatically reselect values that the user chose prior to submitting the form. In ASP.NET, we don’t have to repopulate the database-driven fields if we know that the page has already been populated. Furthermore, selected values stay selected with no manual handlings. The next couple of sections describe the concept in more detail.
The Page class exposes events such as Init, Load, PreRender, and Unload. Your job as a developer is to handle these events and perform the appropriate task for each of these stages. This is much better than the linear execution model in ASP programming, because you don’t have to worry about the location of your initialization scripts.
The first event that happens in the life of a Web Form is the Init event. This is raised so that we can have initialization code for the page. The controls on the page are not yet created at this point. This event is raised once for each user of the page.
The Load event follows the Init event. Subsequently, it is raised each time the page is requested. When this event is raised, all child controls of the Web Form are loaded and accessible. You should be able to retrieve data and populate the controls so that they can render themselves on the page when sent back to the client.
The following example shows the how the Init and Load events can be handled in ASP.NET. In this example, we show both the HTML and its code together in one file to make it simpler:
<html> <head><title>Testing Page Events</title></head> <body> <script language="C#" runat=server> void Page_Init(Object oSender, EventArgs oEvent) { labelInit.Text = DateTime.Now.ToString( ); } void Page_Load(Object oSender, EventArgs oEvent) { labelLoad.Text = DateTime.Now.ToString( ); if(IsPostBack) { labelLoad.Text += "(PostBack)"; } } </script> <form runat=server> Init Time: <asp:Label id=labelInit runat=server/><br/> Load Time: <asp:Label id=labelLoad runat=server/><br/> <input type=submit /> </form> </body> </html>
The first time you access this page, the Init event happens, followed by the Load event. Because these events happen rather quickly, both the Init Time and Load Time would probably show the same time. When you click on the submit button to cause the page to reload, you can see that the Init Time stays what it was, but the Load Time changes each time the page is reloaded.
The PreRender event happens just before the page is rendered and sent back to the client. We don’t often handle this event; however, it depends on the situation.
The last event in the life of a Web Form is the Unload event. This happens when the page is unloaded from memory. Final cleanup should be done here.
Beside these page-level events, controls on the page can also raise events such as ServerClick and ServerChange for HtmlControls, as well as Click, Command, CheckedChanged, SelectedIndexChanged, TextChanged events for WebControls. It is the handling of these events that makes ASP.NET truly dynamic and interactive.
In ASP, the web page starts its life when a client requests a particular page. IIS parses and runs the scripts on the ASP page to render HTML content. As soon as the page rendering is complete, the page’s life ceases. If you have forms that pass data back to the ASP page to be processed, the ASP page runs as a new request, not knowing anything about its previous states. Passing data back to the original page for processing is also referred to as postback.
In ASP.NET, things are a little different. The page still starts at the client’s request; however, it stays around for as long as the client is still interacting with the page. For simplicity’s sake, we say that the page stays around, but in fact, only the view states of the page persist between requests to the page. These view states allow the controls on the server to appear as if they are still present to handle server events. We can detect this postback state of the page via the IsPostBack property of the Page object and forego certain costly reinitialization. The handling of events during these postbacks is what makes ASP.NET so much different than conventional ASP development.
In the following example, we extend the previous example to handle
the postback. When the Load event is handled for the first time, we
populate the drop-down list box with data. Subsequently, we indicate
only the time the event is raised without reloading the data. This
example also demonstrates the server event handler
handleButtonClick
that was bound to the
ServerClick
event of the button:
<html> <head><title>Testing Page Events</title></head> <body> <script language="C#" runat=server> void Page_Init(Object oSender, EventArgs oEvent) { labelInit.Text = DateTime.Now.ToString( ); } void Page_Load(Object oSender, EventArgs oEvent) { labelLoad.Text = DateTime.Now.ToString( ); if(!IsPostBack) { selectCtrl.Items.Add("Acura"); selectCtrl.Items.Add("BMW"); selectCtrl.Items.Add("Cadillac"); selectCtrl.Items.Add("Mercedes"); selectCtrl.Items.Add("Porche"); } else { labelLoad.Text += " (Postback)"; } } void handleButtonClick(Object oSender, EventArgs oEvent) { labelOutput.Text = "You've selected: " + selectCtrl.Value; labelEvent.Text = DateTime.Now.ToString( ); } </script> <form runat=server> Init Time: <asp:Label id=labelInit runat=server/><br/> Load Time: <asp:Label id=labelLoad runat=server/><br/> Event Time: <asp:Label id=labelEvent runat=server/><br/> Choice: <select id=selectCtrl runat=server></select><br/> <asp:Label id=labelOutput runat=server/><br/> <input type=button value=update OnServerClick="handleButtonClick" runat=server /> </form> </body> </html>
The life cycle of a Web Form consists of three main stages: Configuration, Event Handling, and Termination. As mentioned earlier, these stages span across many requests to the same page, as opposed to the serving-one-page-at-a-time policy found in ASP.
In the Configuration stage, the page’s Load event is raised. It is your job to handle this event to set up your page. Because the Load event is raised when all the controls are already up and ready, your job is now to read and update control properties as part of setting up the page. In the previous code example, we handled the Load event to populate the drop-down list with some data. We also updated the labelLoad control’s Text to display the time the Load event happens. In your application, you will probably load the data from a database and initialize form fields with default values.
The page’s IsPostBack property indicates whether this is the
first time the page is loaded or if it is a postback. For example, if
you have a control that contains a list of information, you will only
want to load this control the first time the page is loaded by
checking the IsPostBack property of the page. When IsPostBack is
true
, you know that the list control object is
already loaded with information. There is no need to repopulate the
list. In the previous code example, we skipped over the population of
the drop-down and just displayed a string
"(postback)"
.
You might need to perform data binding and re-evaluate data-binding expressions on the first and subsequent round trips to this page.
In this middle stage, the page’s server event-handling functions are being called as the result of some events being triggered from the client side. These events are from the controls you’ve placed on the Web Form. Figure 7-5 depicts the life cycle of an event.
At this stage, the page has finished rendering and is ready to be discarded. You are responsible for cleaning up file handles, releasing database connections, and freeing objects. Even though you can rely on the CLR to perform garbage collection for you, we strongly advise you to clean up after yourself because garbage collection only happens periodically. On heavily loaded systems, if the garbage-collection cycle is not optimal, the unfreed resources can exhaust memory and bring your system to a halt.
We can perform the clean up for the previous example with the Unload event handler as shown here. Because there is nothing to clean up in this simple example, we just show you the function as a template:
void Page_Unload(Object oSender, EventArgs oEvent) { // cleaning up code here }
As we saw from the System.Web.UI.HtmlControls and System.Web.UI. WebControls namespaces, server controls are programmable controls that run on the server before the page is rendered by ASP.NET. They manage their own states between requests to the same page on the server by inserting a hidden field storing the view state of the form. This eliminates the need to repopulate the value of form fields with the posted value before sending the page back the client.
Server controls are also browser independent. Because they are run on the server side, they can rely on the Request.Browser property to get the client’s capability and render appropriate HTML.
Since the server controls are just instantiations of .NET classes, programming the server controls yields easy-to-maintain code. Especially when you have custom server controls that encapsulate other controls, web application programming becomes nothing more than gluing these blocks together.
All HTML controls and web controls mentioned in System.Web.UI.HtmlControls and System.Web.UI.WebControls are server controls shipped with ASP.NET.
As
you
become more familiar with the ASP.NET framework and the use of server
controls on your Web Form, you will eventually need to know how to
develop these server controls yourself. In ASP.NET, there are two
ways of creating custom server controls: the
pagelet
approach, which is easy to do but rather
limited in functionality, and the Control base class (or UserControl)
derivative
approach, which is more complicated
but also more powerful and flexible.
Until recently, code reuse in ASP development has
been in the form of server-side includes. If you have common UI
blocks or scripts, you can factor them into an include file. Use the
syntax <!--
#include
file="
url
"
-->
to include the common file into the main
page to return to the browser. This approach is fine, but it has
serious limitations. The main thing is to make sure the HTML tag IDs
and script variable names are unique. This is because
IIS does nothing more than merge the
include file when it parses server-side includes. The include file
ends up being in the same scope with the container file. You cannot
include the same file twice because there will be tag ID and script
conflicts.
With ASP.NET, you can factor out common HTML and scripts into what is currently called a pagelet and reuse it without worrying about the ID conflicts. A pagelet is a Web Form, without a body or a form tag, that is accompanied by scripts. The HTML portion of the pagelet is responsible for the layout and the user interface, while the scripts provide the pagelet with programmability by exposing properties and methods. Because the pagelet is considered a user control, it provides an independent scope. You can insert more than one instance of the user control without any problem.
The container Web Form must register the pagelet as a user control
using the
@Register
directive and
then include it on the page with the
<
prefix
:
tagname
>
syntax. If more than one copy of the pagelet is used in a container
page, each of them should be given different IDs for the container
page’s script to work correctly. The script on the container
Web Form can access and manipulate the pagelet the same way it does
any other server controls.
The following example shows how an address form is reused as a pagelet. You might display this address form to allow the web user to register with your application or to display the shipping and billing addresses when the web user checks out:
<table> <tr> <td><asp:Label id=labelName runat="server">Name</asp:Label></td> <td><asp:TextBox id=txtUserName runat="server" Width="332" Height="24"></asp:TextBox></td> </tr> <tr> <td><asp:Label id=labelAddr1 runat="server">Address</asp:Label></td> <td><asp:TextBox id=txtAddr1 runat="server" Width="332" Height="24"></asp:TextBox></td> </tr> <tr> <td><asp:Label id=labelAddr2 runat="server"></asp:Label></td> <td><asp:TextBox id=txtAddr2 runat="server" Width="332" Height="24"></asp:TextBox></td> </tr> <tr> <td><asp:Label id=labelCity runat="server">City</asp:Label></td> <td> <asp:TextBox id=txtCity runat="server"></asp:TextBox> <asp:Label id=labelState runat="server">State</asp:Label> <asp:TextBox id=txtState runat="server" Width="34" Height="24"> </asp:TextBox> <asp:Label id=labelZIP runat="server">ZIP</asp:Label> <asp:TextBox id=txtZIP runat="server" Width="60" Height="24"> </asp:TextBox> </td> </tr> <tr> <td><asp:Label id=labelEmail runat="server">Email</asp:Label></td> <td><asp:TextBox id=txtEmail runat="server" Width="332" Height="24"></asp:TextBox></td> </tr> </table> <script language="C#" runat="server" ID=Script1> public String UserName { get { return txtUserName.Text; } set { txtUserName.Text = value; } } public String Address1 { get { return txtAddr1.Text; } set { txtAddr1.Text = value; } } public String Address2 { get { return txtAddr2.Text; } set { txtAddr2.Text = value; } } public String City { get { return txtCity.Text; } set { txtCity.Text = value; } } public String State { get { return txtState.Text; } set { txtState.Text = value; } } public String ZIP { get { return txtZIP.Text; } set { txtZIP.Text = value; } } </script>
To use your pagelet, register it as a server control via the
@Register
directive, as shown in the next block of
code. After registering, include the tag for the pagelet as if it was
a normal server control. Specify the prefix, the tag name, the server
control’s ID, and set the runat
property to
server
:
<%@ Register TagPrefix="Acme" TagName="Address" Src="Address.ascx" %>
<%@ Page language="c#"%>
<html>
<head>
<script language="C#" runat=server>
void Page_Load(Object oSender, EventArgs evt) {
addr.UserName = "Jack Daniel";
}
</script>
</head>
<body>
Welcome to the E-Shop.
Registering with E-Shop will allow for monthly updates of bargains...
<form method="post" runat="server">
<p><Acme:Address id=addr runat="server"></Acme:Address>
</p>
<p><asp:Button id=cmdClear runat="server" Text="Clear"></asp:Button>
<asp:Button id=cmdSubmit runat="server" Text="Submit">
</asp:Button></p>
</form>
</body>
</html>
You should be able to programmatically access the properties of the
pagelet through the server control’s ID, which is
addr
in this case. In the previous example, we
accessed the UserName property of the Address pagelet via its ID:
addr.UserName = "Jack Daniel";
For an e-commerce checkout page, you could have two instances of
<Acme:Address>
on the same page: one for the
billing and the other for the shipping address. Your script should
access these instances of the pagelet via the ID you assign to each
address control.
You can also programmatically instantiate instances of the pagelet
through the use of the Page’s LoadControl method. The first
thing is to declare a variable of type Control in your script to host
your pagelet. This is because the Control is the root of all objects,
including your pagelet. Then instantiate the variable with a call to
the LoadControl, passing in the filename of the control page. To make
the control visible on the page, add the control to the Page’s
collection of controls. Because you currently have an instance of the
Control object, you won’t be able to call the pagelet’s
properties and methods until you cast the variable from Control type
to your pagelet type. This is similar to having an Object variable in
Visual Basic to hold a COM component. To access the COM-component
methods and properties, you would cast the Object variable to the
component type. As currently implemented, pagelets when loaded are
automatically typed as pagename_extension
. For
example, if your pagelet were named
myControl.ascx
, the type generated for it would
be myControl_ascx
. The boldface line in the
following example shows you how to cast addr1
from
Control to type Address_ascx
in order for you to
access the UserName property of the pagelet:
<%@ Register TagPrefix="Acme" TagName="Address" Src="Address.ascx" %>
<%@ Page language="C#" %>
<html>
<head>
<script language="C#" runat=server>
void Page_Load(Object oSender, EventArgs evt) {
addr.UserName = "Jack Daniel";
Control addr1;
addr1 = LoadControl("Address.ascx"); ((Address_ascx)addr1).UserName = addr.UserName;
this.frm.Controls.AddAt(3, addr1);
}
</script>
</head>
<body>
<form id=frm method="post" runat="server">
Billing Address:<br/>
<Acme:Address id=addr runat="server"></Acme:Address>
Shipping Address:<br/>
<p><asp:Button id=cmdClear runat="server" Text="Clear"></asp:Button>
<asp:Button id=cmdSubmit runat="server" Text="Submit"></asp:Button></p>
</form>
</body>
</html>
This example, the checkout page, shows you how to declare a pagelet
statically in your page with the
<Acme:Address>
tag, as well as how to
dynamically create an instance of the custom control Address with the
Page’s LoadControl( ) method. Once you’ve
created the control dynamically, you must cast the object to the
control type before manipulating it.
The AddAt( )
method is used to insert the Address pagelet at a particular location
in the checkout page. Instead of declaring the dynamic pagelet as a
Control, you can also declare it as its type, which is
Address_ascx
. This way, you just have to cast it
once when loading the dynamic control:
Address_ascx addr2 = (Address_ascx)LoadControl("Address.ascx"); addr2.UserName = "ABC";
While it is easy to create custom controls using the pagelet approach, this technique is not flexible enough to create more powerful custom controls, such as ones that expose events or hierarchy of controls. With ASP.NET, you can also create custom controls by inheriting from the Control base class and overriding a couple of methods.
The following example shows you how to create the simplest custom control as a Control derivative.
namespace MyWebControls { using System; using System.Web.UI; using System.Web.UI.WebControls; using System.ComponentModel; public class MyWebControl : System.Web.UI.WebControls.WebControl { //protected override void Render(HtmlTextWriter output) //{ // output.Write("custom control testing via Render( )"); //} protected override void CreateChildControls( ) { Table tbl = new Table( ); TableRow row = new TableRow( ); TableCell cell = new TableCell( ); HyperLink a = new HyperLink( ); a.NavigateUrl = "http://msdn.microsoft.com"; a.ImageUrl = "image url"; cell.Controls.Add (a); row.Cells.Add(cell); tbl.Rows.Add(row); row = new TableRow( ); cell = new TableCell( ); cell.Controls.Add (new LiteralControl("custom control testing")); row.Cells.Add(cell); tbl.Rows.Add(row); tbl.BorderWidth = 1; tbl.BorderStyle = BorderStyle.Ridge; Controls.Add(tbl); } } }
As you can see, the MyWebControl object derives from the WebControl
class. We have seen that WebControl ultimately derives from the base
Control class. All we really do here is override either the Render or
the CreateChildControls methods to construct the custom web control.
If you choose to override the Render method, you will have to
generate the HTML for your custom control through the HtmlTextWriter
object, output
. You can use methods such
as Write, WriteBeginTag, WriteAttribute, and WriteEndTag.
In our example, we override the CreateChildControls method. Instead of worrying about the actual HTML tag and attribute names, we create ASP.NET objects directly by their class names, such as Table, TableRow, TableCell, HyperLink, and LiteralControl, to construct a hierarchy of objects under a table . We can also manipulate attributes for the objects via their properties. At the end of the method, we add the table to the custom control’s collection of controls.
You will have to compile the previous control code to generate a DLL
assembly. To use the control, just deploy the assembly by copying it
to the /bin
directory of your web application.
Then you should be able to register the control with the
@Register
directive and use the control as if it
was a server control provided by ASP.NET. If you are using Visual
Studio.NET, you can add a reference to the control assembly file or
the control project for the test web project that uses the control.
Your custom-control test page should now look like the following:
<%@ Page language="c#"%> <%@ Register TagPrefix="WC" Namespace="MyWebControls" Assembly="MyWebControls%> <html> <head> <script language="C#" runat=server> void Page_Load(object sender, EventArgs e) { MyWebControls.MyWebControl myCtrl; myCtrl = new MyWebControls.MyWebControl( ); this.Controls.Add(myCtrl); } </script> </head> <body> <form method="post" runat="server"> This is the main page <WC:MyWebControl id=myControl1 runat="server" /> </form> </body> </html>
As you can see, we register the custom control with the
@Register
directive and alias the namespace
MyWebControls with the WC
prefix. In the body of
the Web Form, we can add the custom-control tag as
<WC:MyWebControl>
.
In addition to inserting the custom control onto the page declaratively as shown earlier, we can also programmatically create the custom control at runtime. The Page_Load code demonstrates this point:
MyWebControls.MyWebControl myCtrl; myCtrl = new MyWebControls.MyWebControl( ); this.Controls.Add(myCtrl);
The output page is shown in Figure 7-6.
There are two ways to associate event handlers—functions that handle the event—to the UI controls.
Refer to the earlier section on Section 7.4 particularly where we describe the syntax for server controls. All we
do to bind an event from a control to an event handler is to use the
eventname
=
eventhandlername
attribute/value pair for the control. For example, if we want to
handle the onclick
event for the HTML control
input, all we do is the following. Note that for the HTML controls,
the server-side click event is named
onserverclick
, as opposed to the client-side click
event onclick
, which can still be used in DHTML
scripting:
<input id="cmd1" runat="server"onserverclick="OnClickHandler"
type="button" value="click me">
For an ASP.NET web control, the syntax is the same:
<asp:Button id="cmd2" runat="server"onclick="OnclickHandler2"
Text="click me too"></asp:Button>
After binding the event to the event-handling function name, we have to provide the actual event handler:
void OnClickHandler(object sender, EventArgs e) { // code to retrieve and process the posted data }
The second way of binding events is delegation. You don’t have
to have any notion of code in the aspx
file, not
even the event-handling function name. All you have to do is to
register the event handler with the control’s event-handler
property. For web controls, the event handler property
for
button click is Click
. For HTML controls,
it’s ServerClick
:
ControlID.Click += new System.EventHandler (this.EventHandlerName); ControlID.ServerClick += new System.EventHandler (this.EventHandlerName);
[44] The Web Application directory is the root virtual directory where your web application resides. To set up the virtual directory, use the IIS Administration Tool.
3.133.119.157