Skip to content

2. A Quick Introduction to ASP.NET

Here, we aim to introduce, using a few examples, the ASP.NET concepts that will be useful later in this document. This introduction does not cover the intricacies of client/server communication in a web application. For that, you can read:

This introduction is intended for those who want to get started quickly, accepting—at least initially—that some potentially important points may be left unexplained. The rest of this document will explore these points in greater depth. Those familiar with ASP.NET can skip directly to Section 3.

2.1. A Sample Project

2.1.1. Creating the project

  • In [1], create a new project with Visual Web Developer
  • In [2], select a Visual C# web project
  • In [3], specify that you want to create an ASP.NET web application
  • In [4], name the application. A folder will be created for the project with this name.
  • In [5], specify the parent folder of the project folder [4]
  • In [6], the created project
  • [Default.aspx] is a web page created by default. It contains HTML tags and ASP.NET tags
  • [Default.aspx.cs] contains the code for handling user-triggered events on the [Default.aspx] page displayed in the browser
  • [Default.aspx.designer.cs] contains the list of ASP.NET components for the [Default.aspx] page. Each ASP.NET component placed on the [Default.aspx] page generates a declaration for that component in [Default.aspx.designer.cs].
  • [Web.config] is the configuration file for the ASP.NET project.
  • [References] is the list of DLLs used by the web project. These DLLs are class libraries that the project needs to use. In [7] is the list of DLLs included by default in the project references. Most of them are unnecessary. If the project needs to use a DLL not listed in [7], it can be added via [8].

2.1.2. The [Default.aspx] page

If you run the project using [Ctrl-F5], the [Default.aspx] page is displayed in a browser:

  • in [1], the web project’s URL. Visual Web Developer has a built-in web server that launches when you run a project. It listens on a random port, in this case 1490. The listening port is usually port 80. In [1], no page is requested. In this case, the [Default.aspx] page is displayed, hence its name as the default page.
  • In [2], the [Default.aspx] page is empty.
  • In Visual Web Developer, the [Default.aspx] page [3] can be built visually (the [Design] tab) or using tags (the [Source] tab)
  • In [4], the [Default.aspx] page in [Design] mode. It is built by dragging and dropping components found in the toolbox [5].

[Source] mode [6] provides access to the page's source code:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
  </div>
  </form>
</body>
</html>
  • Line 1 is an ASP.NET directive that lists certain properties of the page
    • The Page directive applies to a web page. There are other directives such as Application, WebService, etc., which apply to other ASP.NET objects
    • The CodeBehind attribute specifies the file that handles the page's events
    • The Language attribute specifies the .NET language used by the CodeBehind file
    • The Inherits attribute specifies the name of the class defined within the CodeBehind file
    • The AutoEventWireUp="true" attribute indicates that the binding between an event in [Default.aspx] and its handler in [Default.aspx.cs] is done by the event name. Thus, the Load event on the [Default.aspx] page will be handled by the Page_Load method of the Intro._Default class defined by the Inherits attribute.
  • Lines 4–14 describe the [Default.aspx] page using tags:
    • Classic HTML tags such as the <body> or <div> tags
    • ASP.NET tags. These are the tags with the runat="server" attribute. ASP.NET tags are processed by the web server before the page is sent to the client. They are converted into HTML tags. The client browser therefore receives a standard HTML page in which there are no longer any ASP.NET tags.

The [Default.aspx] page can be modified directly from its source code. This is sometimes simpler than using [Design] mode. We modify the source code as follows:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Introduction to ASP.NET</title>
</head>
<body>
  <h3>Introduction to ASP.NET</h3>
  <form id="form1" runat="server">
  <div>
  </div>
  </form>
</body>
</html>

On line 6, we give the page a title using the HTML tag <title>. On line 9, we insert text into the body (<body>) of the page. If we run the project (Ctrl-F5), we get the following result in the browser:

 

2.1.3. The [Default.aspx.designer.cs] and [Default.aspx.cs] files

The [Default.aspx.designer.cs] file declares the components of the [Default.aspx] page:


//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime version: 2.0.50727.3603
//
//     Changes made to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace Intro {
    
    
    public partial class _Default {
        
        /// <summary>
        /// form1 control.
        /// </summary>
        /// <remarks>
        /// Automatically generated field.
        /// To modify, move the field declaration from the designer file to the code-behind file.
        /// </remarks>
        protected global::System.Web.UI.HtmlControls.HtmlForm form1;
    }
}

This file contains the list of ASP.NET components on the [Default.aspx] page that have an identifier. They correspond to the tags in [Default.aspx] that have the runat="server" attribute and the id attribute. Thus, the component on line 23 above corresponds to the tag


  <form id="form1" runat="server">

from [Default.aspx].

The developer rarely interacts with the [Default.aspx.designer.cs] file. However, this file is useful for determining the class of a specific component. As shown below, the form1 component is of type HtmlForm. The developer can then explore this class to learn about its properties and methods. The components on the [Default.aspx] page are used by the class in the [Default.aspx.cs] file:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {

    }
  }
}

Note that the class defined in the [Default.aspx.cs] and [Default.aspx.designer.cs] files is the same (line 10): Intro._Default. It is the partial keyword that makes it possible to extend a class declaration across multiple files, in this case two.

In line 10 above, we see that the [_Default] class extends the [Page] class and inherits its events. One of these is the Load event, which occurs when the page is loaded by the web server. On line 12, the Page_Load method handles the page’s Load event. This is generally where the page is initialized before it is displayed in the client’s browser. Here, the Page_Load method does nothing.

The class associated with a web page—in this case, the Intro._Default class—is created at the start of the client request and destroyed once the response has been sent to the client. It cannot therefore be used to store information between requests. To do this, you must use the concept of a user session.

2.2. Events of an ASP.NET Web Page

We are building the following [Default.aspx] page:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Introduction to ASP.NET</title>
</head>
<body>
  <h3>Introduction to ASP.NET</h3>
  <form id="form1" runat="server">
  <div>
    <table>
      <tr>
        <td>
          Name</td>
        <td>
          <asp:TextBox ID="TextBoxName" runat="server"></asp:TextBox>
        </td>
        <td>
          &nbsp;</td>
      </tr>
      <tr>
        <td>
          Age</td>
        <td>
          <asp:TextBox ID="TextBoxAge" runat="server"></asp:TextBox>
        </td>
        <td>
          &nbsp;</td>
      </tr>
    </table>
  </div>
  <asp:Button ID="ButtonValider" runat="server" Text="Validate" />
  <hr />
  <p>
    Events Handled by the Server</p>
  <p>
    <asp:ListBox ID="ListBoxEvts" runat="server"></asp:ListBox>
  </p>
  </form>
</body>
</html>

The [Design] view of the page is as follows:

 

The [Default.aspx.designer.cs] file is as follows:


namespace Intro {
    public partial class _Default {
        protected global::System.Web.UI.HtmlControls.HtmlForm form1;
        protected global::System.Web.UI.WebControls.TextBox TextBoxName;
        protected global::System.Web.UI.WebControls.TextBox TextBoxAge;
        protected global::System.Web.UI.WebControls.Button ButtonValidate;
        protected global::System.Web.UI.WebControls.ListBox ListBoxEvts;
    }
}

This contains all ASP.NET components from the [Default.aspx] page that have an identifier.

We modify the [Default.aspx.cs] file as follows:


using System;

namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Init(object sender, EventArgs e)
    {
      // Log the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Init", DateTime.Now.ToString("hh:mm:ss")));
    }

    protected void Page_Load(object sender, EventArgs e)
    {
      // Log the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
    }

    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // Log the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
    }
  }
}

The [_Default] class (line 5) handles three events:

  • the Init event (line 7), which occurs when the page has been initialized
  • the Load event (line 13), which occurs when the page has been loaded by the web server. The Init event occurs before the Load event.
  • the Click event on the ButtonValider button (line 19), which occurs when the user clicks the [Validate] button

Handling each of these three events involves adding a message to the Listbox component named ListBoxEvts. This message displays the event’s time and name. Each message is placed at the top of the list. Therefore, the messages at the top of the list are the most recent.

When the project is run, the following page is displayed:

We can see in [1] that the Page_Init and Page_Load events occurred in that order. Remember that the most recent event is at the top of the list. When the browser requests the [Default.aspx] page directly via its URL [2], it does so using an HTTP (HyperText Transfer Protocol) command called GET. Once the page has loaded in the browser, the user will trigger events on the page. For example, they will click the [Submit] button [3]. The events triggered by the user once the page has loaded in the browser initiate a request to the [Default.aspx] page, but this time using an HTTP command called POST. To summarize:

  • the initial loading of a page P in a browser is done via an HTTP GET operation
  • the events that occur subsequently on the page generate a new request to the same page P each time, but this time using an HTTP POST command. A page P can determine whether it was requested using a GET or a POST command, allowing it to behave differently if necessary—which is usually the case.

Initial request for an ASPX page: GET

  • In [1], the browser requests the ASPX page via an HTTP GET request without parameters.
  • In [2], the web server sends back the HTML output of the requested ASPX page.

Handling an event triggered on the page displayed by the browser: POST

  • In [1], when an event occurs on the HTML page, the browser requests the ASPX page that was previously retrieved via a GET operation, this time using an HTTP POST request accompanied by parameters. These parameters are the values of the components located within the <form> tag of the HTML page displayed by the browser. These values are called client-posted values. They will be used by the ASPX page to process the client’s request.
  • In [2], the web server sends back the HTML output of the ASPX page initially requested via POST, or of another page if a page transfer or redirection occurred.

Let’s return to our example page:

  • In [2], the page was retrieved via a GET request.
  • In [1], we see the two events that occurred during this GET

If, above, the user clicks the [Validate] button [3], the [Default.aspx] page will be requested via a POST request. This POST will be accompanied by parameters that are the values of all components included in the <form> tag of the [Default.aspx] page: the two TextBoxes [TextBoxName, TextBoxAge], the [SubmitButton], and the list [EventListBox]. The posted values for the components are as follows:

  • TextBox: the entered value
  • Button: the button text, in this case "Validate"
  • ListBox: the text of the message selected in the ListBox

In response to the POST, we get page [4]. This is again the [Default.aspx] page. This is normal behavior, unless there is a page transfer or redirection by the page’s event handlers. We can see that two new events have occurred:

  • the Page_Load event, which occurred when the page loaded
  • the ButtonValider_Click event, which occurred when the [Validate] button was clicked

Note that:

  • the Page_Init event did not occur during the HTTP POST operation, whereas it did occur during the HTTP GET operation
  • the Page_Load event occurs every time, whether it’s a GET or a POST. It is in this method that we generally need to know whether we are dealing with a GET or a POST.
  • After the POST, the [Default.aspx] page was sent back to the client with the changes made by the event handlers. This is always the case. Once the events of a page P have been processed, that same page P is sent back to the client. There are two ways to break this rule. The last event handler executed can
    • transfer the execution flow to another page P2.
    • redirect the client browser to another page P2.

In both cases, it is page P2 that is sent back to the browser. The two methods have differences that we will discuss later.

  • The ButtonValider_Click event occurred after the Page_Load event. Therefore, it is this handler that can decide whether to transfer or redirect to a page P2.
  • The event list [4] retained the two events displayed during the initial GET load of the [Default.aspx] page. This is surprising given that the [Default.aspx] page was recreated during the POST. We should see the [Default.aspx] page with its design values, and thus an empty ListBox. The execution of the Page_Load and ButtonValider_Click handlers should then populate it with two messages. However, there are four. This is explained by the VIEWSTATE mechanism. During the initial GET request, the web server sends the [Default.aspx] page with an HTML tag <input type="hidden" ...> called a hidden field (line 10 below).
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>
        Introduction to ASP.NET
</title></head>
<body>
  <h3>Introduction to ASP.NET</h3>
  <form name="form1" method="post" action="default.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEPDwUKLTMzMTEyNDMxMg9kFgICAw9kFgICBw8QZBAVAhMwNjoxNjozNjogUGFnZV9Mb2FkEzA2OjE2OjM2OiBQYWdlX0luaXQVAhMwNjoxNjozNjogUGFnZV9Mb2FkEzA2OjE2OjM2OiBQYWdlX0luaXQUKwMCZ2dkZGRW1AnTL8f/q7h2MXBLxctKD1UKfg==" />
</div>
..............................

In the "__VIEWSTATE" ID field, the web server encodes the values of all the page's components. It does this for both the initial GET request and the subsequent POST requests. When a POST request is made to page P:

  • the browser requests page P by sending the values of all components inside the <form> tag in its request. Above, we can see that the "__VIEWSTATE" component is inside the <form> tag. Its value is therefore sent to the server during a POST request.
  • Page P is instantiated and initialized with its constructor values
  • The "__VIEWSTATE" component is used to restore the values the components had when page P was previously submitted. This is how, for example, the list of events [4] retrieves the first two messages it had when it was sent in response to the browser’s initial GET request.
  • The components of page P then take on the values posted by the browser. At this point, the form on page P is in the state in which the user posted it.
  • The Page_Load event is processed. Here, it adds a message to the list of events [4].
  • The event that triggered the POST request is handled. Here, ButtonValider_Click adds a message to the event list[4].
  • Page P is returned. The components have the following values:
    • either the posted value, i.e., the value the component had in the form when it was posted to the server
    • or a value provided by one of the event handlers.

In our example,

  • the two TextBox components will retain their posted values because the event handlers do not modify them
  • the list of events [4] regains its posted value, i.e., all events already listed, plus two new events created by the Page_Load and ButtonValider_Click methods.

The VIEWSTATE mechanism can be enabled or disabled at the component level. Let’s disable it for the [ListBoxEvts] component:

  • In [1], the VIEWSTATE of the [ListBoxEvts] component is disabled. That of the TextBox [2] is enabled by default.
  • In [3], the two events returned after the initial GET
  • In [4], we have filled out the form and clicked the [Validate] button. A POST request to the [Default.aspx] page will be sent.
  • In [6], the result returned after clicking the [Validate] button
  • The mechanism of the enabled VIEWSTATE explains why the TextBoxes [7] retained their values posted in [4]
  • The disabled VIEWSTATE mechanism explains why the [ListBoxEvts] component [8] did not retain its content [5].

2.3. Handling Posted Values

Here, we will focus on the values posted by the two TextBoxes when the user clicks the [Validate] button. The [Default.aspx] page in [Design] mode changes as follows:

The source code for the element added in [1] is as follows:


  <p>
    Elements posted to the server:
    <asp:Label ID="LabelPost" runat="server"></asp:Label>
</p>

We will use the [LabelPost] component to display the values entered in the two TextBoxes [2]. The code for the event handler [Default.aspx.cs] changes as follows:


using System;

namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
    protected void Page_Init(object sender, EventArgs e)
    {
      // Log the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Init", DateTime.Now.ToString("hh:mm:ss")));
    }

    protected void Page_Load(object sender, EventArgs e)
    {
      // Log the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
    }

    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // log the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
      // display the name and age
      LabelPost.Text = string.Format("name={0}, age={1}", TextBoxName.Text.Trim(), TextBoxAge.Text.Trim());
    }
  }
}

Line 24, we update the LabelPost component:

  • LabelPost is of type [System.Web.UI.WebControls.Label] (see Default.aspx.designer.cs). Its Text property represents the text displayed by the component.
  • TextBoxName and TextBoxAge are of type [System.Web.UI.WebControls.TextBox]. The Text property of a TextBox component is the text displayed in the input field.
  • The Trim() method removes any spaces that may precede or follow a string

As explained earlier, when the ButtonValider_Click method is executed, the page components have the values they had when the page was submitted by the user. The Text properties of the two TextBoxes therefore contain the text entered by the user in the browser.

Here is an example:

  • in [1], the posted values
  • in [2], the server's response.
  • in [3], the TextBoxes have regained their posted values via the activated VIEWSTATE mechanism
  • in [4], the messages from the ListBoxEvts component come from the Page_Init, Page_Load, and ButtonValider_Click methods and from a disabled VIEWSTATE
  • in [5], the LabelPost component obtained its value via the ButtonValider_Click method. We have successfully retrieved the two values entered by the user in the two TextBoxes [1].

As shown above, the posted value for age is the string "yy," an invalid value. We will add components called validators to the page. They are used to verify the validity of posted data. This validity can be verified in two places:

  • on the client side. A validator configuration option allows you to choose whether or not to perform the checks in the browser. They are then performed by JavaScript code embedded in the HTML page. When the user submits the values entered in the form, they are first checked by the JavaScript code. If any of the checks fail, the submission is not performed. This avoids a round trip to the server, making the page more responsive.
  • on the server. While client-side validation may be optional, server-side validation is mandatory regardless of whether client-side validation was performed. This is because when a page receives submitted values, it has no way of knowing whether they were validated by the client before being sent. On the server side, the developer must therefore always verify the validity of the submitted data.

The [Default.aspx] page evolves as follows:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="Intro._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title>Introduction to ASP.NET</title>
</head>
<body>
  <h3>Introduction to ASP.NET</h3>
  <form id="form1" runat="server">
  <div>
    <table>
      <tr>
        <td>
          Name</td>
        <td>
          <asp:TextBox ID="TextBoxName" runat="server"></asp:TextBox>
        </td>
        <td>
          <asp:RequiredFieldValidator ID="RequiredFieldValidatorName" runat="server" 
            ControlToValidate="TextBoxName" Display="Dynamic" 
            ErrorMessage="Required field!"></asp:RequiredFieldValidator>
        </td>
      </tr>
      <tr>
        <td>
          Age</td>
        <td>
          <asp:TextBox ID="TextBoxAge" runat="server"></asp:TextBox>
        </td>
        <td>
          <asp:RequiredFieldValidator ID="RequiredFieldValidatorAge" runat="server" 
            ControlToValidate="TextBoxAge" Display="Dynamic" 
            ErrorMessage="Required field!"></asp:RequiredFieldValidator>
          <asp:RangeValidator ID="RangeValidatorAge" runat="server" 
            ControlToValidate="TextBoxAge" Display="Dynamic" 
            ErrorMessage="Enter a number between 1 and 150!" MaximumValue="150" 
            MinimumValue="1" Type="Integer"></asp:RangeValidator>
        </td>
      </tr>
    </table>
  </div>
  <asp:Button ID="ButtonValider" runat="server" onclick="ButtonValider_Click" 
    Text="Validate" CausesValidation="False"/>
  <hr />
  <p>
    Events handled by the server</p>
  <p>
    <asp:ListBox ID="ListBoxEvts" runat="server" EnableViewState="False">
    </asp:ListBox>
  </p>
  <p>
    Elements posted to the server:
    <asp:Label ID="LabelPost" runat="server"></asp:Label>
  </p>
  <p>
    Elements validated by the server:
    <asp:Label ID="LabelValidation" runat="server"></asp:Label>
  </p>
  <asp:Label ID="InputErrorsLabel" runat="server" ForeColor="Red"></asp:Label>
  </form>
</body>
</html>

Validators have been added to lines 20, 32, and 35. On line 58, a Label control is used to display the valid submitted values. On line 60, a Label control is used to display an error message if there are input errors.

The [Default.aspx] page in [Design] mode looks like this:

  • Components [1] and [2] are of the RequiredFieldValidator type. This validator checks that an input field is not empty.
  • Component [3] is a RangeValidator. This validator checks that an input field contains a value between two limits.
  • In [4], the properties of validator [1].

We will demonstrate the two types of validators using their tags in the code for the [Default.aspx] page:


          <asp:RequiredFieldValidator ID="RequiredFieldValidatorNom" runat="server" 
            ControlToValidate="TextBoxName" Display="Dynamic" 
ErrorMessage="Required field!"></asp:RequiredFieldValidator>
  • ID: the component's identifier
  • ControlToValidate: the name of the component whose value is being validated. Here, we want to ensure that the TextBoxNom component does not have an empty value (empty string or a sequence of spaces)
  • ErrorMessage: error message to display in the validator if the data is invalid.
  • EnableClientScript: a Boolean indicating whether the validator should also be executed on the client side. This attribute has a default value of True when not explicitly set as shown above.
  • Display: display mode of the validator. There are two modes:
    • static (default): the validator takes up space on the page even if it does not display an error message
    • dynamic: the validator does not take up space on the page if it does not display an error message.

          <asp:RangeValidator ID="RangeValidatorAge" runat="server" 
            ControlToValidate="TextBoxAge" Display="Dynamic" 
            ErrorMessage="Enter a number between 1 and 150!" MaximumValue="150" 
            MinimumValue="1" Type="Integer"></asp:RangeValidator>
  • Type: the type of the data being validated. Here, the age is an integer.
  • MinimumValue, MaximumValue: the limits within which the validated value must fall

The configuration of the component that triggers the POST plays a role in the validation mode. Here, this component is the [Validate] button:


  <asp:Button ID="ButtonValider" runat="server" onclick="ButtonValider_Click"  Text="Validate" CausesValidation="True" />
  • CausesValidation: sets the automatic mode or name of server-side validations. This attribute has the default value "True" if not explicitly specified. In this case,
    • on the client side, validators with EnableClientScript set to True are executed. The POST request is only sent if all client-side validators succeed.
    • On the server side, all validators on the page are automatically executed before the event that triggered the POST is processed. In this case, they would be executed before the ButtonValider_Click method is called. Within this method, you can determine whether all validations were successful or not. Page.IsValid is "True" if they all succeeded, "False" otherwise. In the latter case, you can stop processing the event that triggered the POST. The posted page is returned exactly as it was submitted. The validators that failed then display their error message (ErrorMessage attribute).

If CausesValidation is set to False, then

  • on the client side, no validators are executed
  • on the server side, it is up to the developer to request the execution of the page’s validators. This is done using the Page.Validate() method. Depending on the validation results, this method sets the Page.IsValid property to "True" or "False".

In [Default.aspx.cs], the code for the ButtonValider_Click event evolves as follows:


protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // log the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
      // display the name and age
      LabelPost.Text = string.Format("name={0}, age={1}", TextBoxName.Text.Trim(), TextBoxAge.Text.Trim());
      // Is the page valid?
      Page.Validate();
      if (!Page.IsValid)
      {
        // global error message
        InputErrorsLabel.Text = "Please correct the input errors...";
        InputErrorsLabel.Visible = true;
        return;
      }
      // hide the error message
      InputErrorsLabel.Visible = false;
      // display the validated name and age
      LabelValidation.Text = string.Format("name={0}, age={1}", TextBoxName.Text.Trim(), TextBoxAge.Text.Trim());
}

If the [Validate] button has its CausesValidation attribute set to True and the validators have their EnableClientScript attribute set to True, the ButtonValider_Click method is executed only when the posted values are valid. One might then wonder about the purpose of the code starting on line 8. It is important to remember that it is always possible to write a client-side script that posts unverified values to the [Default.aspx] page. Therefore, this page must always re-run the validity checks.

  • Line 8: triggers the execution of all validators on the page. If the [Validate] button has its CausesValidation attribute set to True, this is done automatically, and there is no need to repeat it. There is redundancy here.
  • Lines 9–15: Case where one of the validators has failed
  • Lines 16–19: case where all validators have passed

Here are two examples of execution:

  • in [1], an example of execution in the case where:
    • the [Validate] button has its CausesValidation property set to True
    • The validators have their EnableClientScript property set to True

The error messages [2] were displayed by the validators executed on the client side by the page's JavaScript code. No POST request was sent to the server, as shown by the label of the posted elements [3].

  • In [4], an example of execution in the case where:
    • the [Validate] button has its CausesValidation property set to False
    • the validators have their EnableClientScript property set to False

The error messages [5] were displayed by the validators executed on the server side. As shown in [6], a POST request was indeed sent to the server. In [7], the error message displayed by the [ButtonValider_Click] method in the case of input errors.

  • In [8], an example obtained with valid data. [9,10] show that the posted elements have been validated. When performing repeated tests, set the EnableViewState property of the [LabelValidation] label to False so that the validation message does not remain displayed across runs.

2.4. Managing Application-Scope Data

Let’s revisit the execution architecture of an ASPX page:

The ASPX page class is instantiated at the start of the client request and destroyed at the end of it. Therefore, it cannot be used to store data between requests. You may want to store two types of data:

  • data shared by all users of the web application. This is generally read-only data. Three files are used to implement this data sharing:
    • [Web.Config]: the application configuration file
    • [Global.asax, Global.asax.cs]: allow you to define a class, called the global application class, whose lifetime is that of the application, as well as handlers for certain events of that same application.

The global application class allows you to define data that will be available for all requests from all users.

  • data shared across requests from the same client. This data is stored in an object called a Session. We refer to this as the client session to denote the client’s memory. All requests from a client have access to this session. They can store and read information there

Above, we show the types of memory an ASPX page has access to:

  • the application memory, which mostly contains read-only data and is accessible to all users.
  • a specific user’s memory, or session, which contains read/write data and is accessible to successive requests from the same user.
  • Not shown above, there is a request memory, or request context. A user’s request may be processed by several successive ASPX pages. The request context allows Page 1 to pass information to Page 2.

Here we are interested in Application-scope data, which is shared by all users. The application’s global class can be created as follows:

  • In [1], add a new element to the project
  • In [2], add the global application class
  • in [3], keep the default name [Global.asax] for the new element
  • in [4], two new files have been added to the project
  • in [5], display the markup for [Global.asax]

<%@ Application Codebehind="Global.asax.cs" Inherits="Intro.Global" Language="C#" %>
  • The Application tag replaces the Page tag we had for [Default.aspx]. It identifies the global application class
  • Codebehind: defines the file in which the global application class is defined
  • Inherits: defines the name of this class

The generated Intro.Global class is as follows:


using System;

namespace Intro
{
  public class Global : System.Web.HttpApplication
  {

    protected void Application_Start(object sender, EventArgs e)
    {

    }

    protected void Session_Start(object sender, EventArgs e)
    {

    }

    protected void Application_BeginRequest(object sender, EventArgs e)
    {

    }

    protected void Application_AuthenticateRequest(object sender, EventArgs e)
    {

    }

    protected void Application_Error(object sender, EventArgs e)
    {

    }

    protected void Session_End(object sender, EventArgs e)
    {

    }

    protected void Application_End(object sender, EventArgs e)
    {

    }
  }
}
  • Line 5: The global application class derives from the HttpApplication class

The class is generated with application event handler templates:

  • lines 8, 38: handle the Application_Start (application startup) and Application_End (application shutdown when the web server stops or when the administrator unloads the application) events
  • lines 13, 33: handle the Session_Start event (start of a new client session when a new client arrives or when an existing session expires) and the Session_End event (end of a client session, either explicitly via programming or implicitly by exceeding the allowed session duration).
  • Line 28: Handles the Application_Error event (occurrence of an exception not handled by the application code and propagated up to the server)
  • Line 18: Handles the Application_BeginRequest event (arrival of a new request).
  • Line 23: Handles the Application_AuthenticateRequest event (occurs when a user has authenticated).

The [Application_Start] method is often used to initialize the application based on information contained in [Web.Config]. The one generated upon initial project creation looks like this:


<?xml version="1.0" encoding="utf-8"?>

<configuration>
    <configSections>
...
    </configSections>  
  
    <appSettings/>
    <connectionStrings/>
  
    <system.web>
...
    </system.web>

    <system.codedom>
....
    </system.codedom>
    
    <!-- 
        The system.webServer section is required to run ASP.NET AJAX on Internet
        Information Services 7.0.  It is not required for earlier versions of IIS.
    -->
    <system.webServer>
...
    </system.webServer>

    <runtime>
....
    </runtime>

</configuration>

For our current application, this file is unnecessary. If we delete or rename it, the application continues to function normally. We will focus on the tags in lines 8 and 9:

  • <appsettings> allows you to define a dictionary of information
  • <connectionStrings> allows you to define connection strings to databases

Consider the following [Web.config] file:


<?xml version="1.0" encoding="utf-8"?>

<configuration>
    <configSections>
...
    </configSections>  
  
  <appSettings>
    <add key="key1" value="value1"/>
    <add key="key2" value="value2"/>
  </appSettings>
  <connectionStrings>
    <add connectionString="connectionString1" name="conn1"/>
  </connectionStrings>
  
    <system.web>
...

This file can be used by the following global application class:


using System;
using System.Configuration;

namespace Intro
{
  public class Global : System.Web.HttpApplication
  {
    public static string Param1 { get; set; }
    public static string Param2 { get; set; }
    public static string ConnString1 { get; set; }
    public static string Error { get; set; }

    protected void Application_Start(object sender, EventArgs e)
    {
      try
      {
        Param1 = ConfigurationManager.AppSettings["key1"];
        Param2 = ConfigurationManager.AppSettings["key2"];
        ConnString1 = ConfigurationManager.ConnectionStrings["conn1"].ConnectionString;
      }
      catch (Exception ex)
      {
        Error = string.Format("Configuration error: {0}", ex.Message);
      }
    }

    protected void Session_Start(object sender, EventArgs e)
    {

    }

  }
}
  • lines 8–11: four static properties P. Since the Global class has the same lifetime as the application, any request made to the application will have access to these P properties via the Global.P syntax.
  • lines 17-19: the [Web.config] file is accessible via the [System.Configuration.ConfigurationManager] class
  • lines 17-18: retrieves the elements of the <appSettings> tag from the [Web.config] file via the key attribute.
  • Line 19: Retrieves the elements of the <connectionStrings> tag from the [Web.config] file via the name attribute.

The static attributes in lines 8–11 are accessible from any event handler in loaded ASPX pages. We use them in the [Page_Load] handler of the [Default.aspx] page:


    protected void Page_Load(object sender, EventArgs e)
    {
      // log the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: Page_Load", DateTime.Now.ToString("hh:mm:ss")));
      // retrieve information from the global application class
      LabelGlobal.Text = string.Format("Param1={0},Param2={1},ConnString1={2},Error={3}", Global.Param1, Global.Param2, Global.ConnString1, Global.Error);
}
  • Line 6: The four static properties of the global application class are used to populate a new label on the [Default.aspx] page

At runtime, we get the following result:

Above, we can see that the parameters from [web.config] have been correctly retrieved. The global application class is the right place to store information shared by all users.

2.5. Managing Session-Scoped Data

Here, we are interested in how to store information across requests from a given user:

Each user has their own memory, known as their session.

We have seen that the global application class has two handlers for managing events:

  • Session_Start: start of a session
  • Session_end: end of a session

The session mechanism works as follows:

  • When a user makes their first request, the web server creates a session token and assigns it to the user. This token is a unique string of characters for each user. It is sent by the server in the response to the user’s first request.
  • In subsequent requests, the user (the web browser) includes the assigned session token in their request. This allows the web server to recognize them.
  • A session has a timeout period. When the web server receives a request from a user, it calculates the time that has elapsed since the previous request. If this time exceeds the session timeout, a new session is created for the user. The data from the previous session is lost. With Microsoft’s IIS (Internet Information Server) web server, sessions have a default lifetime of 20 minutes. This value can be changed by the web server administrator.
  • The web server knows it is handling a user’s first request because that request does not contain a session token. It is the only one.

Any ASP.NET page has access to the user’s session via the page’s Session property, of type [System.Web.SessionState.HttpSessionState]. We will use the following P properties and M methods of the HttpSessionState class:

Name
Type
Role
Item[String key]
P
The session can be structured as a dictionary. Item[key] is the session element identified by key. Instead of writing [HttpSessionState].Item[key], you can also write [HttpSessionState].[key].
Clear
M
clears the session dictionary
Abandon
M
ends the session. The session is then no longer valid. A new session will start with the user's next request.

As an example of user state, we will count the number of times a user clicks the [Submit] button. To achieve this, we need to maintain a counter in the user's session.

The [Default.aspx] page changes as follows:

The global application class [Global.asax.cs] changes as follows:


using System;
using System.Configuration;

namespace Intro
{
  public class Global : System.Web.HttpApplication
  {
    public static string Param1 { get; set; }
...

    protected void Application_Start(object sender, EventArgs e)
    {
...
    }

    protected void Session_Start(object sender, EventArgs e)
    {
      // request counter
      Session["nbRequests"] = 0;
    }

  }
}

On line 19, we use the user's session to store a request counter identified by the key "nbRequests". This counter is updated by the [ButtonValider_Click] handler on the [Default.aspx] page:


using System;

namespace Intro
{
  public partial class _Default : System.Web.UI.Page
  {
....

    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // Log the event
      ListBoxEvts.Items.Insert(0, string.Format("{0}: ButtonValider_Click", DateTime.Now.ToString("hh:mm:ss")));
      // display the posted name and age
      LabelPost.Text = string.Format("name={0}, age={1}", TextBoxName.Text.Trim(), TextBoxAge.Text.Trim());
      // number of requests
      Session["nbRequests"] = (int)Session["nbRequests"] + 1;
      LabelNumberOfRequests.Text = Session["NumberOfRequests"].ToString();
      // Is the page valid?
      Page.Validate();
      if (!Page.IsValid)
      {
...
      }
...
    }
  }
}
  • line 16: increment the request counter
  • line 17: the counter is displayed on the page

Here is an example of execution:

2.6. Handling GET / POST in page loading

We mentioned that there are two types of requests to an ASPX page:

  • the initial request from the browser made with an HTTP GET command. The server responds by sending the requested page. We will assume that this page is a form, i.e., that the sent ASPX page contains a <form runat="server"> tag.
  • Subsequent requests made by the browser in response to certain user actions on the form. The browser then makes an HTTP POST request.

Whether it is a GET request or a POST request, the [Page_Load] method is executed. During a GET request, this method is typically used to initialize the page sent to the client browser. Subsequently, through the VIEWSTATE mechanism, the page remains initialized and is modified only by the event handlers that trigger POST requests. There is no need to reinitialize the page in Page_Load. Hence the need for this method to determine whether the client request is a GET or a POST.

Let’s examine the following example. We add a drop-down list to the [Default.aspx] page. The content of this list will be defined in the Page_Load handler for the GET request:

The drop-down list is declared in [Default.aspx.designer.cs] as follows:


        protected global::System.Web.UI.WebControls.DropDownList DropDownListNames;

We will use the following M methods and P properties of the [DropDownList] class:

Name
Type
Role
Items
P
the ListItemCollection of ListItem elements in the drop-down list
SelectedIndex
P
the index, starting at 0, of the selected item in the drop-down list when the form is submitted
SelectedItem
P
the ListItem selected in the drop-down list when the form is submitted
SelectedValue
P
the string value of the ListItem selected in the drop-down list when the form is submitted. We will define this concept of value shortly.

The ListItem class for the items in a drop-down list is used to generate the <option> tags within the HTML <select> tag:

1
2
3
4
5
<select ....>
    <option value="val1">text1</option>
    <option value="val2">text2</option>
....
</select>

In the <option> tag

  • text1 is the text displayed in the drop-down list
  • vali is the value posted by the browser if texti is the text selected in the drop-down list

Each option can be generated by a ListItem object created using the ListItem(string text, string value) constructor.

In [Default.aspx.cs], the code for the [Page_Load] handler changes as follows:


    protected void Page_Load(object sender, EventArgs e)
    {
      // we handle the event
      ...
      // retrieve information from the global application class
      ...
      // Initialize the name combo box only during the initial GET request
      if (!IsPostBack)
      {
        for (int i = 0; i < 3; i++)
        {
          DropDownListNames.Items.Add(new ListItem("name" + i, i.ToString()));
        }
      }
}
  • Line 8: The Page class has a boolean IsPostBack property. Essentially, this means that the user's request is a POST. Lines 10–13 are therefore executed only on the client's initial GET request.
  • Line 12: We add an element of type ListItem(string text, string value) to the [DropDownListNames] list. The text displayed for the (i+1)th element will be "names", and the value posted for this element if it is selected will be i.

The [ButtonValider_Click] handler is modified to display the value posted by the drop-down list:


    protected void ButtonValider_Click(object sender, EventArgs e)
    {
      // register the event
...
      // display the posted values
      LabelPost.Text = string.Format("name={0}, age={1}, combo={2}", TextBoxName.Text.Trim(), TextBoxAge.Text.Trim(), DropDownListNames.SelectedValue);
      // number of requests
...
}

Line 6: The posted value for the [DropDownListNames] list is obtained using the list's SelectedValue property. Here is an example of execution:

  • in [1], the content of the drop-down list after the initial GET and just before the first POST
  • in [2], the page after the first POST.
  • in [3], the value submitted for the drop-down list. This corresponds to the value attribute of the selected ListItem in the list.
  • in [4], the drop-down list. It contains the same items as after the initial GET request. This is due to the VIEWSTATE mechanism.

To understand the interaction between the VIEWSTATE of the DropDownListNames list and the if (!IsPostBack) test in the Page_Load handler of [Default.aspx], the reader is invited to repeat the previous test with the following configurations:

Case
DropDownListNames.EnableViewState
if(! IsPostBack) test in Page_Load of [Default.aspx]
1
true
present
2
false
present
3
true
absent
4
false
absent

The various tests yield the following results:

  1. This is the case described above
  2. The list is populated during the initial GET but not during the subsequent POSTs. Since EnableViewState is set to false, the list is empty after each POST
  3. The list is populated both after the initial GET and during subsequent POST requests. Since EnableViewState is set to true, there are 3 names after the initial GET, 6 names after the first POST, 9 names after the second POST, ...
  4. The list is populated both after the initial GET and during the subsequent POSTs. Since EnableViewState is set to false, the list is populated with only 3 names for each request, whether it is the initial GET or the subsequent POSTs. We see the same behavior as in Case 1. There are therefore two ways to achieve the same result.

2.7. Managing the VIEWSTATE of elements on an ASPX page

By default, all elements on an ASPX page have their EnableViewState property set to True. Every time the ASPX page is sent to the client browser, it contains the hidden field __VIEWSTATE, whose value is a string encoding all the values of the components with their EnableViewState property set to True. To minimize the size of this string, we can try to reduce the number of components with their EnableViewState property set to True.

Let’s review how components on an ASPX page obtain their values following a POST request:

  1. The ASPX page is instantiated. The components are initialized with their design values.
  2. The __VIEWSTATE value posted by the browser is used to give the components the values they had when the ASPX page was sent to the browser the previous time.
  3. The values posted by the browser are assigned to the components.
  4. Event handlers are executed. They may modify the values of certain components.

From this sequence, we can deduce that components that:

  • have their values posted
  • have their values modified by an event handler

may have their EnableViewState property set to False since their VIEWSTATE value (step 2) will be modified by either step 3 or 4.

The list of components on our page is available in [Default.aspx.designer.cs]:


namespace Intro {
    public partial class _Default {
        protected global::System.Web.UI.HtmlControls.HtmlForm form1;
        protected global::System.Web.UI.WebControls.TextBox TextBoxName;
        protected global::System.Web.UI.WebControls.RequiredFieldValidator RequiredFieldValidatorName;
        protected global::System.Web.UI.WebControls.TextBox TextBoxAge;
        protected global::System.Web.UI.WebControls.RequiredFieldValidator RequiredFieldValidatorAge;
        protected global::System.Web.UI.WebControls.RangeValidator RangeValidatorAge;
        protected global::System.Web.UI.WebControls.DropDownList DropDownListNames;
        protected global::System.Web.UI.WebControls.Button ButtonValidate;
        protected global::System.Web.UI.WebControls.ListBox ListBoxEvts;
        protected global::System.Web.UI.WebControls.Label LabelPost;
        protected global::System.Web.UI.WebControls.Label LabelValidation;
        protected global::System.Web.UI.WebControls.Label InputErrors;
        protected global::System.Web.UI.WebControls.Label LabelGlobal;
        protected global::System.Web.UI.WebControls.Label LabelNumberOfRequests;
    }
}

The value of the EnableViewState property for these components could be as follows:

Component
Posted value
EnableViewState
Why
TextBoxName
Value entered in the TextBox
False
the component's value is posted
TextBoxAge
same
  
RequiredFieldValidatorName
none
False
No concept of component value
RequiredFieldValidatorAge
same
  
RangeValidatorAge
same
  
LabelPost
none
False
gets its value from an event handler
LabelValidation
same
  
InputErrorLabel
same
  
GlobalLabel
same
  
LabelNumberOfRequests
same
  
DropDownListNames
"value" of the selected item
True
We want to keep the list's content across requests without having to regenerate it
ListBoxEvts
"value" of the selected item
False
The list's content is generated by an event handler
ButtonValidate
button label
False
The component retains its design value

2.8. Forwarding from one page to another

Until now, GET and POST requests always returned the same page [Default.aspx]. We will consider the case where a request is processed by two successive ASPX pages, [Default.aspx] and [Page1.aspx], and where the latter is returned to the client. Additionally, we will see how the [Default.aspx] page can pass information to the [Page1.aspx] page via a memory we will call the request memory.

We build the [Page1.aspx] page:

  • In [1], we add a new element to the project
  • In [2], we add a [Web Form] element named [Page1.aspx] [3]
  • in [4], the added page
  • in [5], the page once built

The source code for [Page1.aspx] is as follows:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Page1.aspx.cs" Inherits="Intro.Page1" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>Page1</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h1>
      Page 1</h1>
    <asp:Label ID="Label1" runat="server"></asp:Label>
    <br />
    <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/Default.aspx">Back 
      to page [Default]</asp:HyperLink>
  </div>
  </form>
</body>
</html>
  • Line 13: a label that will be used to display information sent by the [Default.aspx] page
  • Line 15: an HTML link to the [Default.aspx] page. When the user clicks this link, the browser requests the [Default.aspx] page using a GET operation. The [Default.aspx] page is then loaded as if the user had typed its URL directly into the browser.

The [Default.aspx] page is enhanced with a new LinkButton component:

The source code for this new component is as follows:


  <asp:LinkButton ID="LinkButtonToPage1" runat="server" CausesValidation="False" 
    EnableViewState="False" onclick="LinkButtonToPage1_Click">Forward to Page1</asp:LinkButton>
  • CausesValidation="False": Clicking the link will trigger a POST request to [Default.aspx]. The [LinkButton] component behaves like the [Button] component. Here, we do not want clicking the link to trigger the execution of validators.
  • EnableViewState="False": there is no need to preserve the link's state across requests. It retains its design values.
  • onclick="LinkButtonToPage1_Click": name of the method that, in [Defaul.aspx.cs], handles the Click event on the LinkButtonToPage1 component.

The code for the LinkButtonToPage1_Click handler is as follows:


  // to Page1
  protected void LinkButtonToPage1_Click(object sender, EventArgs e)
  {
    // set information in the context
    Context.Items["msg1"] = "Message from Default.aspx to Page1";
    // forward the request to Page1
    Server.Transfer("Page1.aspx", true);
}

Line 7: The request is passed to the [Page1.aspx] page using the [Server.Transfer] method. The second parameter of the method, set to true, indicates that all the information sent to [Default.aspx] during the POST request must be passed to [Page1.aspx]. This allows [Page1.aspx], for example, to access the posted values via a collection called Request.Form. Line 5 uses what is known as the request context. It is accessed via the Context property of the Page class. This context can serve as a shared memory between the different pages handling the same request, in this case [Default.aspx] and [Page1.aspx]. The Items dictionary is used for this purpose.

When [Page1.aspx] is loaded by the Server.Transfer("Page1.aspx", true) operation, everything happens as if [Page1.aspx] had been called by a GET request from a browser. The Page_Load handler of [Page1.aspx] is executed normally. We will use it to display the message placed by [Default.aspx] in the request context:


using System;

namespace Intro
{
  public partial class Page1 : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      Label1.Text = Context.Items["msg1"] as string;
    }
  }
}

Line 9: The message set by [Default.aspx] in the request context is displayed in Label1.

Here is an example of execution:

  • On the [Default.aspx] page [1], click the link [2] that takes you to the Page1 page
  • in [3], the Page1 page is displayed
  • At [4], the message created in [Default.aspx] and displayed by [Page1.aspx]
  • At [5], the URL displayed in the browser is that of the [Default.aspx] page

2.9. Redirecting from one page to another

Here we present another technique that is functionally similar to the previous one: when the user requests the [Default.aspx] page via a POST request, they receive another page [Page2.aspx] in response. In the previous method, the user’s request was processed successively by two pages: [Default.aspx] and [Page1.aspx]. In the page redirection method we are now presenting, there are two separate requests from the browser:

  • In [1], the browser sends a POST request to the page [Default.aspx]. This page processes the request and sends a so-called redirect response to the browser. This response is a simple HTTP stream (lines of text) instructing the browser to redirect to another URL [Page2.aspx]. [Default.aspx] does not send any HTML content in this first response.
  • In [2], the browser makes a GET request to the page [Page2.aspx]. This page is then sent as a response to the browser.
  • If the page [Default.aspx] wants to pass information to the page [Page2.aspx], it can do so via the user’s session. Unlike the previous method, the request context cannot be used here, as there are two separate requests and therefore two separate contexts. We must therefore use the user’s session to enable the pages to communicate with each other.

As was done for [Page1.aspx], we add the [Page2.aspx] page to the project:

  • in [1], [Page2.aspx] was added to the project
  • in [2], the visual appearance of [Page2.aspx]
  • in [3], we add a LinkButton component [4] to the [Default.aspx] page, which will redirect the user to [Page2.aspx].

The source code for [Page2.aspx] is similar to that of [Page1.aspx]:


<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Page2.aspx.cs" Inherits="Intro.Page2" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>Page2</title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <h1>
      Page 2</h1>
    <asp:Label ID="Label1" runat="server"></asp:Label>
    <br />
    <asp:HyperLink ID="HyperLink1" runat="server" NavigateUrl="~/Default.aspx">Back 
      to page [Default]</asp:HyperLink>
  </div>
  </form>
</body>
</html>

In [Default.aspx], adding the LinkButton component generated the following source code:


<asp:LinkButton ID="LinkButtonToPage2" runat="server" 
    onclick="LinkButtonToPage2_Click">Redirect to Page2</asp:LinkButton>

The [LinkButtonToPage2_Click] handler handles the redirection to [Page2.aspx]. Its code in [Default.aspx.cs] is as follows:


    protected void LinkButtonToPage2_Click(object sender, EventArgs e)
    {
      // We set a message in the session
      Session["msg2"] = "Message from [Default.aspx] to [Page2.aspx]";
      // redirect the client to [Page2.aspx]
      Response.Redirect("Page2.aspx");
}
  • Line 4: We set a message in the user's session
  • line 5: The Response object is a property of every ASPX page. It represents the response sent to the client. It has a Redirect method that causes the response sent to the client to be an HTTP redirect request.

When the browser receives the redirect command to [Page2.aspx], it will send a GET request to that page. On that page, the [Page_Load] method will execute. We will use it to retrieve the message stored by [Default.aspx] in the session and display it. The code for [Page2.aspx.cs] is as follows:


using System;

namespace Intro
{
  public partial class Page2 : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      // Display the message placed in the session by [Default.aspx]
      Label1.Text = Session["msg2"] as string;
    }
  }
}

Upon execution, the following results are obtained:

  • In [1], we click the redirect link on [Default.aspx]. A POST request is sent to the [Default.aspx] page
  • In [2], the browser has been redirected to [Page2.aspx]. This can be seen in the URL displayed by the browser. In the previous method, this URL was that of [Default.aspx] because the only request made by the browser was to that URL. Here, there is a first POST request to [Default.aspx], followed by a second GET request to [Page2.aspx] without the user’s knowledge.
  • In [3], we see that [Page2.aspx] has correctly retrieved the message placed by [Default.aspx] in the session.

2.10. Conclusion

We have introduced, using a few examples, the ASP.NET concepts that will be useful to us in the rest of this document. This introduction does not cover the subtleties of client/server communication in a web application. For that, you can read: