Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

ASP.NET AJAX: State or no State?

4.49/5 (14 votes)
2 Jan 200710 min read 2   386  
An article covering the two different design paths we can take when using the ASP.NET AJAX framework

Introduction

In this article we are going to look at two possible scenarios we may find ourselves in when using the ASP.NET AJAX (Asynchronous JavaScript and XML) framework – whether we require state or not.

State?

In ASP.NET we can use and create controls which maintain their state over Hypertext Transfer Protocol (HTTP) requests. A good example of a control that uses state is the GridView control. The GridView has a view and edit state, and if you have filtering and paging enabled you have even more states. In ASP.NET the state of a control is maintained by the ViewState property. ViewState allows us to maintain the values of our controls over HTTP requests, delivering the same user interface (UI) consistency of a desktop application.

When we require state

As we have identified earlier the GridView is a good example of a control that has many states, invoking each particular state requires a full Postback to the server which breaks the application UI consistency with the dreaded screen flicker.

With the introduction of ASP.NET AJAX (previously codenamed Atlas) the screen flicker commonly associated with a Postback to the server was solved by the introduction of the UpdatePanel. From a high level view you can think of the UpdatePanel providing asynchronous updates for the content you have wrapped in the UpdatePanel control.

Something which should be noted is that the use of the UpdatePanel still causes a full Postback to the server, just like it would do if we were not using the UpdatePanel. The beauty of the UpdatePanel is that it preserves the Page event model, so we can raise events from controls wrapped in the UpdatePanel and benefit from asynchronous processing of data.

Let us take a look at using the GridView within an UpdatePanel and identify what data is being requested from the server.

Figure 1: Using a GridView within an UpdatePanel control

HTML
<asp:ScriptManager 
    ID="sm" 
    runat="server" />
    
<asp:UpdatePanel 
    ID="upGv" 
    runat="server">
    <ContentTemplate>
        <asp:GridView 
            ID="gvPeople" 
            runat="server" 
            AllowPaging="True" 
            AllowSorting="True"
            AutoGenerateColumns="False" 
            DataKeyNames="personid" 
            DataSourceID="odsPeople" 
            Width="400px">
            <Columns>
                <asp:CommandField 
                    ShowEditButton="True" />
                <asp:CommandField 
                    ShowDeleteButton="true" />
                <asp:BoundField 
                    DataField="firstname" 
                    HeaderText="First Name" 
                    SortExpression="firstname" />
                <asp:BoundField 
                    DataField="lastname" 
                    HeaderText="Last Name" 
                    SortExpression="lastname" />
            </Columns>
        </asp:GridView>
    </ContentTemplate>
</asp:UpdatePanel>

In Fig. 1 we describe a GridView control within an UpdatePanels’ ContentTemplate element – that is all we need to do in order to benefit from the asynchronous update of the GridViews’ content.

There is a key component of Fig. 1 which we have yet to explain and that is the ScriptManager control, this is the brain behind ASP.NET AJAX – it decides what scripts to download to the client in order for the AJAX functionality to work on the clients’ browser (IE, Safari, Firefox, Mozilla, etc...).

If we run Fig. 1 then you will see no screen flicker, more important, however, is the content which is being requested from the server when we page through our data. In Fig. 2 we can see some of the data that has been requested by the client as we viewed another page of data in our GridView control. The first line of the response (887 | updatePanel | upGv) tells us that the total size of the updated content for the UpdatePanel control was 887 bytes and that the ID of the associated UpatePanel is upGv. As we continue to look at what was sent back to the client, we notice that only the updated HTML code for the GridView was sent to the client, any other objects on the page outside of the UpdatePanel were not re-downloaded.

Figure 2: Data requested by the client for GridView control

Image 1

Latency issues

Because we are issuing asynchronous requests to the server we need to deal with latency. In AJAX latency is a major factor we must take into consideration, when a user initiates something we need to inform the user of the progress of that request – remember that the latency our user will experience will vary depending on the stress our server is under.

The ASP.NET AJAX bits come with an UpdateProgress control which allows us to inform the user in a friendly manner that the request has been initiated successfully and the request is in progress, when the request is complete the message that we pre-define disappears.

Let us look at the example in Fig. 3 which is the same as Fig. 1, but with an UpdateProgress control added.

Figure 3: Using the UpdateProgress control

HTML
<asp:ScriptManager 
    ID="sm" 
    runat="server" />
    
<asp:UpdatePanel 
    ID="upGv" 
    runat="server">
    <ContentTemplate>
        <asp:GridView 
            ID="gvPeople" 
            runat="server" 
            AllowPaging="True" 
            AllowSorting="True"
            AutoGenerateColumns="False" 
            DataKeyNames="personid" 
            DataSourceID="odsPeople" 
            Width="400px">
            <Columns>
                <asp:CommandField 
                    ShowEditButton="True" />
                <asp:CommandField 
                    ShowDeleteButton="true" />
                <asp:BoundField 
                    DataField="firstname" 
                    HeaderText="First Name" 
                    SortExpression="firstname" />
                <asp:BoundField 
                    DataField="lastname" 
                    HeaderText="Last Name" 
                    SortExpression="lastname" />
            </Columns>
        </asp:GridView>
    </ContentTemplate>
</asp:UpdatePanel>

<asp:UpdateProgress 
    ID="upProgGv" 
    runat="server">
    <ProgressTemplate>
        <p>Please wait Loading...</p>
    </ProgressTemplate>
</asp:UpdateProgress>

In Fig. 3 we can see that defining the UpdateProgress control is very simple, our user friendly message goes within the ProgressTemplate element of our UpdateProgress control – and with that we have effectively addressed the latency issue of the asynchronous requests invoked by our GridView!

Multiple Controls and the UpdatePanel

So far we have addressed the use of a single control within a single UpdatePanel control, at some point we will want to use the asynchronous behaviour provided by the UpdatePanel with multiple controls.

Implementing this functionality is very simple, the best practice is to logically break your UI into several atomic pieces of functionality and then assign an UpdatePanel to each.

Let us take a look at Fig. 4, this example has a GridView and a FormView on the same page each with its own UpdatePanel and UpdateProgress control.

Figure 4: Multiple UpdatePanel’s

HTML
<asp:ScriptManager 
    ID="sm" 
    runat="server" />
    
<asp:UpdatePanel 
    ID="upGv" 
    runat="server">
    <ContentTemplate>
        <asp:GridView 
            ID="gvPeople" 
            runat="server" 
            AllowPaging="True" 
            AllowSorting="True"
            AutoGenerateColumns="False" 
            DataKeyNames="personid" 
            DataSourceID="odsPeople" 
            Width="400px">
            <Columns>
                <asp:CommandField 
                    ShowEditButton="True" />
                <asp:CommandField 
                    ShowDeleteButton="true" />
                <asp:BoundField 
                    DataField="firstname" 
                    HeaderText="First Name" 
                    SortExpression="firstname" />
                <asp:BoundField 
                    DataField="lastname" 
                    HeaderText="Last Name" 
                    SortExpression="lastname" />
            </Columns>
        </asp:GridView>
    </ContentTemplate>
</asp:UpdatePanel>

<asp:UpdateProgress 
    ID="upProgGv" 
    runat="server" 
    AssociatedUpdatePanelID="upGv">
    <ProgressTemplate>
        <p>Please wait Loading...</p>
    </ProgressTemplate>
</asp:UpdateProgress>

<asp:UpdatePanel 
    ID="upFv" 
    runat="server">
    <ContentTemplate>
        <asp:FormView 
            ID="fvPerson" 
            runat="server" 
            DataSourceID="odsPeople" 
            DefaultMode="Insert">
            <InsertItemTemplate>
                <asp:TextBox
                    ID="txtFirstName" 
                    Text='<%# Bind("firstname") %>' 
                    runat="server" />
                <asp:TextBox 
                    ID="txtLastName"
                    Text='<%# Bind("lastname") %>' 
                    runat="server" />
                <asp:LinkButton 
                    ID="lbInsert" 
                    CommandName="Insert" 
                    Text="Add Person" 
                    runat="server" />
                <asp:LinkButton 
                    ID="lbCancel" 
                    CommandName="Cancel" 
                    Text="Cancel" 
                    runat="server" />
            </InsertItemTemplate>
        </asp:FormView>
    </ContentTemplate>
</asp:UpdatePanel>

<asp:UpdateProgress 
    ID="upProgFv" 
    runat="server" 
    AssociatedUpdatePanelID="upFv">
    <ProgressTemplate>
        <p>Adding Person Please wait...</p>
    </ProgressTemplate>
</asp:UpdateProgress>

If you run the code in Fig. 4 you will notice that each piece of atomic function has its own UpdateProgress control, the UpdateProgress control allows us to explicitly associate it with an UpdatePanel using the AssociateUpdatePanelID property.

Conditional Updates

There will be times when you wish to update the content of an UpdatePanel only when a specific condition is met, ASP.NET AJAX comes with this functionality out of the box. We can define that an UpdatePanels’ content is to be updated by the changing of a property or the raising of an event from a control.

Let us take a look at Fig. 5 which updates the content of the UpdatePanel when the Click event of a button is raised.

Figure 5: Using triggers to conditionally update the content of an UpdatePanel

HTML
<asp:ScriptManager 
    ID="sm" 
    runat="server" />

<asp:UpdatePanel 
    ID="up" 
    runat="server">
    <ContentTemplate>
       <asp:TextBox 
            ID="txtText" 
            runat="server" />
        <asp:Button 
            ID="btnCopy" 
            Text="Copy Text" 
            OnClick="btnCopy_Click" 
            runat="server" />
        <asp:Label 
            ID="lblText" 
            runat="server" />
    </ContentTemplate>
    <Triggers>
        <asp:AsyncPostBackTrigger 
            ControlID="btnCopy" 
            EventName="Click" />
    </Triggers>
</asp:UpdatePanel>
C#
using System;
using System.Web.UI;

public partial class State_ConditionalUpdate : System.Web.UI.Page {

    protected void Page_Load(object sender, EventArgs e) {
    }

    protected void btnCopy_Click(object sender, EventArgs e) {
        lblText.Text = txtText.Text.ToString();
    }

}

When we don’t require state

When we don’t require state to be maintained by our application we can use basic HTML and JavaScript to provide asynchronous calls to code on our server. The benefit of taking the stateless approach is that it is lightweight – we no longer do a full Postback to the server.

ASP.NET AJAX provides a rich framework for calling code on the server using JavaScript. We have the ability to asynchronously call ASP.NET Web Service or WCF Service methods which are hosted on the server – all without doing a roundtrip to the server!

Before we look at any code let us first cover how we can access methods on a Web Service. We have already seen in the state part of this article how important the ScriptManager control is, well it gets better because we can define a Services child element of the ScriptManager control and add a reference to services on the server. In Fig. 6 we add a reference to the People Web Service which is an ASP.NET Web Service.

Figure 6: Referencing a Web Service

HTML
<asp:ScriptManager 
    ID="sm" 
    runat="server">
    <Services>
        <asp:ServiceReference 
            Path="~/WebServices/PeopleWebService.asmx" />
    </Services>
</asp:ScriptManager>

If we run this code we will see a reference to a URL with the same address as the Web Service, however, it will have a /js flag appended on to it. This JavaScript file is an auto generated proxy for calling the Web Services’ methods from JavaScript code on the client. We don’t get this functionality out of the box with Web Services, we have to explicitly define that our Web Service can be called by client JavaScript - this is done using the ScriptService attribute (found in the System.Web.Scripts.Services namespace), Fig. 7 shows this.

Figure 7: Allowing a Web Services’ methods to be called by client JavaScript

C#
[ScriptService]
public class PeopleWebService : System.Web.Services.WebService {
    // ....
}

Now we have our Web Service and ScriptManager all set up, let us create some JavaScript code that will simply retrieve the current date and time from the server (Fig. 8).

Figure 8: Asynchronously calling a method of a Web Service

HTML
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ServerTime.aspx.cs" 
   Inherits="No_State_ServerTime" %>

<!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>Server Time</title>
    <script type="text/javascript">
    function GetServerTime() {
        PeopleWebService.GetServerTime(OnSuccessCallback, OnServiceTimeout, 
                                       OnServiceError);
    }
    
    function OnSuccessCallback(result) {
        alert(result);
    }
    
    function OnServiceTimeout(result) {
        alert(result);
    }
    
    function OnServiceError(result) {
        alert(result);
    }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    
    <asp:ScriptManager 
        ID="sm" 
        runat="server">
        <Services>
            <asp:ServiceReference 
                Path="~/WebServices/PeopleWebService.asmx" />
        </Services>
    </asp:ScriptManager>
    
    <input 
        type="button" 
        value="Get Server Time" 
        onclick="GetServerTime()" />
    
    </form>
</body>
</html>

In Fig. 8 we call a function called GetServerTime() when the button input is clicked, in this function we call the method GetServerTime that is defined in the PeopleWebService.asmx file. In JavaScript we access the method of the service as if the service were a statically defined type (e.g. WebServiceName.NameOfMethod), we pass this method three arguments – the function to call when we get some data sent back from the service, and what to do when the service times out or incurs an error. The OnSuccessCallback function takes a parameter result, result is the variable name for whatever data type we are passed back – it could be an array, integer, etc... In this case we are passed back a string – we then display that string in an alert box (Fig. 9 shows this).

Figure 9: The result of invoking a method of a Web Service

Image 2

Now we have walked through how a method of a Web Service is invoked we need to talk about what data is being sent over the wire to the client and more importantly how. When we invoke a method of a Web Service we use JavaScript Object Notation (JSON) to send the data back to the client. An explanation of why JSON is used is simple, it is more lightweight than sending serialized XML and unlike XML which JavaScript has to parse – JSON can be natively executed (only call code which is secure, we don’t want to send nasty scripts down the wire to our users!).

In Fig. 10 we inspect the data that is sent to the client when the function OnSuccessCallback in Fig. 7 is invoked.

Note: We will look at some more complex data sent over the wire in the next example.

Figure 10: Looking at what is sent over the wire when using JSON

Image 3

At the moment we have only sent a simple string data type back to the client using JSON, now let us send a custom type back to the client.

When sending back complex custom types to the client we need to let the system know what types we intend to send to the client via script, for this we need to add another attribute to our Web Service – GenerateScriptType(typeof(MyType)), this attribute is located in the System.Web.Script.Services namespace. Before we move on we should look at the code behind file for the Person Web Service (Fig. 11).

Figure 11: Code behind for the PersonWebService.asmx

C#
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Web;
using System.Web.Configuration;
using System.Web.Script.Services;
using System.Web.Services;
using System.Web.Services.Protocols;

[WebService(Namespace = "http://gbarnett.org/services")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
[GenerateScriptType(typeof(Person))]
public class PeopleWebService : System.Web.Services.WebService {

    public static string _conn
     = WebConfigurationManager.ConnectionStrings["CodeProjectConn"]
                                                          .ConnectionString;

    public PeopleWebService() { }

    [WebMethod(Description="Gets a list of all the People")]
    public Person[] GetPeople() {
        List<PERSON> personList = new List<PERSON>();
        using (SqlConnection sqlConn = new SqlConnection(_conn))
        using (SqlCommand sqlCmd
          = new SqlCommand("SELECT PersonId, FirstName, LastName FROM People",
          sqlConn)) {
            sqlConn.Open();
            SqlDataReader sqlRdr = sqlCmd.ExecuteReader();
            while (sqlRdr.Read()) {
                personList.Add(new Person((int)sqlRdr.GetInt32(0), 
                               sqlRdr.GetString(1) as string, 
                               sqlRdr.GetString(2) as string));
            }
        }
        return personList.ToArray();
    }

    [WebMethod(Description = "Returns the time on the server")]
    public string GetServerTime() {
        return DateTime.Now.ToLongDateString() + ", " + 
               DateTime.Now.ToLongTimeString();
    }

}

Figure 12: Definition of Person type

C#
using System;

/// <SUMMARY>
/// Person entity
/// </SUMMARY>public class Person {

    private int _personId;
    private string _firstName;
    private string _lastName;

    public Person() { }

    public Person(int personId, string firstName, string lastName) {
        _personId = personId;
        _firstName = firstName;
        _lastName = lastName;
    }

    public int PersonId {
        get { return _personId; }
    }

    public string FirstName {
        get { return _firstName; }
        set { _firstName = value; }
    }

    public string LastName {
        get { return _lastName; }
        set { _lastName = value; }
    }

}

We are interested in the GetPeople method which returns an array of type Person (Person type code shown in Fig. 12). For the next example we are going to call this method (GetPeople) and then iterate through the items outputting a HTML unordered list (Fig. 13).

Figure 13: Outputting a HTML unordered list of People

HTML
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="PeopleList.aspx.cs" 
         Inherits="NoStateViewPeople" %>

<!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>Untitled Page</title>
    
    <script type="text/javascript">
    function GetPeople() {
        PeopleWebService.GetPeople(OnCompleteCallback, OnServiceTimeout, 
                                  OnServiceError);
    }
    
    function OnCompleteCallback(result) {
        for(i = 0; i < result.length; i++) {
            $get("people").innerHTML += "<li>" + result[i].LastName + ", " + 
                                        result[i].FirstName + "</li>";
        }
    }
    
    function OnServiceTimeout(result) {
        alert(result);
    }
    
    function OnServiceError(result) {
        alert(result);
    }
    </script>

</head>
<body>
    
    <form id="form1" runat="server">
    
    <asp:ScriptManager 
        ID="sm" 
        runat="server">
        <Services>
            <asp:ServiceReference 
                Path="~/WebServices/PeopleWebService.asmx" />
        </Services>
    </asp:ScriptManager>
    
    <input 
        type="button" 
        value="Get People" 
        onclick="GetPeople()" />
    
    <ul 
        id="people"></ul>
    
    </form>
</body>
</html>

Because the result of invoking the GetPeople() method returns an array, the type of the result variable is an array of type Person. Also of significance is that we can access the properties of the Person type as we would do using C# code (e.g. result[i].FirstName).

To conclude the use of no state let us see the form of the JSON object graph (Fig. 14) when we invoke the GetPeople() method of the People Web Service.

Figure 14: JSON Object Graph

Image 4

Summary

Throughout this article the power and flexibility of ASP.NET AJAX has been revealed. We have looked at the benefits/disadvantages of using both state and no state approaches when using the ASP.NET AJAX framework, we also inspected the data which was sent across the wire for each approach.

Using the code

Note: You will need .NET 2.0, ASP.NET AJAX RC (or RTM), and SQL Server Express/2005 to run the examples in this article. The SQL scripts for setting the database up as well as all code files are in the .zip file.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here