The typical web form consists of controls (like labels, buttons, and data grids), and programming logic. In ASP.NET 2.0, there are two approaches to managing these control and code pieces: the single-file page model and the code-behind page model. Regardless of which model you choose, it’s important to understand how the runtime is processing and executing your web forms behind the scenes. In this article, we will examine how web forms move from design time to run-time in ASP.NET 2.0.
The Code-Behind Model
Visual Studio creates a web form using the code-behind model when you add a new web form to a project and check the “Place code in separate file” checkbox in the Add New Item dialog. Visual Studio will add two files to the project: an aspx file and a .cs or .vb file. If we were creating a Default.aspx web page, the aspx file would look like the following.
<%@
Page Language="C#" AutoEventWireup="true"
CodeFile="Default.aspx.cs" Inherits="_Default"
%>
<!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>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
</div>
</form>
</body>
</html>
The ASPX page contains the typical markup for a form, including the html, head, and body tags. Any controls we add to the form using the designer will appear inside the ASPX file also. The three key pieces for us to focus on are in the @ Page directive on top. The AutoEventWireup attribute defaults to true, and we will return to this attribute later in the article. The CodeFile attribute is the link to the source code file with the programming logic for this web form, in this case the file is Default.aspx.cs. The runtime will use the CodeFile attribute to determine which source code file it needs to compile for this ASPX page. Likewise, the Inherits attribute tells the runtime the name of the class it will use as a base class for this web form, and again we will see how this works shortly when we see the entire picture. For now, let’s look at the CodeFile (code-behind) file for the ASPX page.
using System;
using System.Web.UI;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
Here we find the _Default class (which we saw in the Inherits attribute) and our standard Page_Load event handler which (note: some non-essential using directives have been removed from the listing to conserve space). We typically use the Page_Load even handler to initialize controls and perform data binding. Currently, in VB.NET, the default .vb file will not contain the Page_Load event handler, but you can easily add the event handler using the page event drop down list just above the text editor. In either language, the key to focus on is the ‘partial’ keyword.
The partial keyword (new in .NET 2.0) is one of the rare keywords that is present in both C# and VB.NET and carries the same meaning (shocker!). Partial allows us to split a class definition across two or more source code files. In previous version of the .NET framework this was impossible to achieve – a class definition had to exist inside of a single .cs or .vb file.
The partial keyword plays an important role because it will allow the runtime to extend the definition of our _Default class with additional members. For instance, any control appearing in the ASPX markup with runat=”server” and an id attribute will ultimately act like a member variable in our class. For example, in the Page_Load event handler we can already access a variable named form1 – form1 represents the HTML form tag in the ASPX, which does have runat=”server” specified. How does all the magic happen? Let’s take a look at what happens when we browse to the web application to execute the form.
Compilation In The Code-Behind Model
When an incoming browser request arrives for our web form, the ASP.NET runtime needs to accomplish two tasks. First, the runtime needs to parse and understand the controls we’ve placed in the ASPX file. Parsing involves reading the ASPX portion of the form, and generating source code to create those controls and HTML markup. The second job is then to compile the generated source code. You can actually see the code the runtime generates by poking around in the ‘Temporary ASP.NET Files’ directory, which will reside underneath the framework installation in the
public partial class _Default : System.Web.SessionState.IRequiresSessionState
{
protected System.Web.UI.HtmlControls.HtmlForm form1;
protected System.Web.Profile.DefaultProfile Profile
{
get
{
return ((System.Web.Profile.DefaultProfile)(this.Context.Profile));
}
}
protected System.Web.HttpApplication ApplicationInstance
{
get
{
return ((System.Web.HttpApplication)(this.Context.ApplicationInstance));
}
}
}
namespace ASP
{
public class Default_aspx : _Default
{
public Default_aspx()
{
// ...
}
protected override void FrameworkInitialize()
{
base.FrameworkInitialize();
this.__BuildControlTree(this);
this.AddWrappedFileDependencies(ASP.Default_aspx.__fileDependencies);
this.Request.ValidateInput();
}
// ...
}
}
The first piece to notice is the partial class definition at the top of the generated code. This partial class will complete the _Default class definition from our Default.aspx.cs code behind file by adding some additional members. One of these members is a field to represent the HTML form tag on the page (again, because it has a runat=”server” tag). Any other server controls we would place on the form would also become members of the class.
The second class (Default_aspx) in this generated file represents the ASPX page itself. This class inherits the _Default class and contains all the code needed to initialize the form, instantiate the server controls, and spit out literal HTML.
The runtime will compile both of these classes (_Default and _Default_aspx) into the same assembly. This assembly will be located in the temporary ASP.NET files directory.
Wired
One last question to answer is this: how does our Page_Load method get invoked? If you examine all of the code in the code-behind and in the generated files you’ll see no use of delegates in C#, nor any code with the Handles clause in VB.NET (these are the constructs generally used to wire up event handlers).
The answer is in the AutoEventWireup attribute back in the @ Page directive for Default.aspx. The AutoEventWireup attribute is set to true (and would default to true if not present). When true, the ASP.NET runtime will attempt to attach page methods to events based on the method names. If the method follows the convention of Page_EventName, then the method will be paired with an event by the name of EventName. For instance, the runtime will wire up Page_Load with the Load event, and Page_Init to the Init event. All of this happens without explicit delegates or Handles clauses.
The Single-File Page Model
In this section we will create a new Default.aspx webform, but leave the “Place code in a separate file” checkbox unchecked. We are also going to drag a Label control onto the form. The IDE will show us an ASPX file with the following contents.
<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<script runat="server">
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
</div>
</form>
</body>
</html>
There is no .cs or .vb file associated with the ASPX file, so the @ Page directive does not need any attributes indicating where the associated source code file resides. All of the logic we want to add to the page can appear inside the server side script tag. For instance, we could change the text of the label control by placing the following code inside the script block.
<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
Label1.Text = "Hello World!";
}
</script>
Remember AutoEventWireup defaults to true when not present in the @ Page directive, so the Page_Load method will automatically wire up to the page object’s load event. But what class is Page_Load a part of? We can see exactly what happens by looking for C# source code files in the temporary ASP.NET files directory when the web form executes. Here is an excerpt.
namespace ASP
{
public class Default_aspx : System.Web.UI.Page,
System.Web.SessionState.IRequiresSessionState
{
protected System.Web.UI.WebControls.Label Label1;
protected System.Web.UI.HtmlControls.HtmlForm form1;
protected void Page_Load(object sender, EventArgs e)
{
Label1.Text = "Hello World!";
}
public Default_aspx()
{
// ...
}
protected override void FrameworkInitialize()
{
base.FrameworkInitialize();
this.__BuildControlTree(this);
this.AddWrappedFileDependencies(ASP.Default_aspx.__fileDependencies);
this.Request.ValidateInput();
}
// ....
}
}
In the single-file page model, the ASP.NET runtime generates code for only a single class (Default_aspx). Compared to the code-behind model, which used two partial class declarations and an inherited class, the single file model does the same amount of work with fewer pieces. However, since the runtime is generating the extra pieces for us, we shouldn’t consider this an advantage.
And The Winner Is…
The obvious question is which model should you be using for your ASP.NET projects? The answer will largely depend on the type of person you are. Working from a single file containing both the code and ASPX markup will appeal to many people, while others will insist on a strict separation and favor the code-behind model. The single-file model has an advantage in configuration management and deployment, since there is only a single file to version and deploy. Intellisense and refactoring tools appear to work equally well with both models, so there will be no clear winner in the productivity category. One additional factor in deciding on the model to use is your pre-compilation strategy, which will be the topic of our next article.
Feel free to leave questions or comments about this article on my blog.