Microsoft & .NET.NET"Atlas" Foundations: ASP.NET 2.0 Callback

“Atlas” Foundations: ASP.NET 2.0 Callback

One of the issues developers had when they first started to develop commercial Web sites was some of the limitations of using a browser as the interface. For instance, there were many cases where you wanted to retrieve information from the server after the user had performed some action, like entering an employee number in a Web page to retrieve the details of an employee. To accomplish this, you would post the current page to the server, retrieve the employee information from the database and refresh the page with the information retrieved from the server. Although this method of refreshing the whole page is very common today, it is inefficient because the Web page refreshes and re-renders the entire page of content, even if only a small percentage of the page has actually changed.

Fortunately ASP.NET 2.0 provides an efficient approach to invoke a remote function from a server page without refreshing the browser. This new feature is called ASP.NET 2.0 Script Callback, and it builds on the foundation of the XmlHttp object library. Using ASP.NET 2.0 script callback feature, you can emulate some of the behaviors of a traditional fat-client application in a Web-based application. It can be used to refresh individual controls, to validate controls, or even to process a form without having to post the whole page to the server. When you utilize script callback approach to retrieve data, you would typically transfer the data in the form of XML stream from the server side to the client and then load the XML data in a client-side XML DOM object to process the XML. In order to understand Microsoft’s “Atlas” which provides an easier way to build these “Ajax” style applications in ASP.NET 2.0, it’s useful to have a strong understanding of ASP.NET 2.0 Callback.

This article will consider an example wherein you retrieve the details of an employee based on the employee number entered in the Web page. To accomplish this, you will leverage the script callback feature and demonstrate how to retrieve the employee details from the AdventureWorks database (that ships with SQL Server 2005) without posting the page back to the server.

Before looking at the example, it is time to examine the steps involved in utilizing the callback feature.

  • The client invokes a client-side method that will use the callback manager.
  • The callback manager creates the request to a .aspx page on the server.
  • The ASP.NET runtime on the server receives the request, processes the request by invoking a predefined sever-side function named RaiseCallbackEvent and passing in the client side argument to it. The RaiseCallbackEvent method stores the client-side arguments in a private variable for later use. After that, the ASP.NET invokes another method named GetCallbackResult(), which is responsible for returning the output of the server-side call to the client-side callback manager.
  • The callback manager receives the server response and invokes a callback method located on the client-side. If there is an error during the server-side processing, the callback manager invokes a separate callback method.
  • The client callback method processes the results of the server-side call.

Now that you have understood the steps, Listing 1 shows an example page that implements callback feature.

Listing 1: Retrieving XML Data Dynamically Using ASP.NET 2.0 Script Callback

<%@ Page language="C#" %>
<%@ Import Namespace="System.Data" %>
<%@ Import Namespace="System.Data.SqlClient" %>
<%@ Import Namespace="System.Xml" %>
<%@ Import Namespace="System.Web.Configuration" %>
<%@ implements interface="System.Web.UI.ICallbackEventHandler" %>

<script runat="server">
  private string _callbackArg;
  void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)
  {
    _callbackArg = eventArgument;
  }

  string ICallbackEventHandler.GetCallbackResult()
  {
    try
    {
      int value = Int32.Parse(_callbackArg);
      return GetEmployeeDetails(value);
    }
    catch (Exception ex)
    {
      throw new ApplicationException
        ("An Error has occurred during the processing " +
        " of your request. Error is :" + ex.Message);
    }
  }
  
   public string GetEmployeeDetails(int employeeID)
   {
     string connString = WebConfigurationManager.
       ConnectionStrings["adventureWorks"].ConnectionString;     
     try
     {
       using(SqlConnection sqlConnection = new SqlConnection(connString))
       {
         DataSet employeeDataset = new DataSet("EmployeesRoot");
         //Pass in the name of the stored 
		 //procedure to be executed and the 
         //SqlConnection object as the argument 
         SqlDataAdapter adapter = new SqlDataAdapter();
         SqlCommand command = 
		 	new SqlCommand("Select EmployeeID, Title, "+
           "CAST(HireDate AS char(12)) AS HireDate, Gender, " +
           "CAST(BirthDate AS char(12)) AS BirthDate from " +
           "HumanResources.Employee 
		   	Where EmployeeID =" + employeeID.ToString(), 
           sqlConnection);
         //Set the SqlCommand object properties
         command.CommandType = CommandType.Text;
         adapter.SelectCommand = command;
         //Fill the Dataset with the 
		 //return value from the stored procedure
         adapter.Fill(employeeDataset,"Employees" );
         XmlDocument xmlDoc = new XmlDocument();
         return employeeDataset.GetXml();
       }
     }
     catch (Exception ex)
     {
       throw ex;
     }
   }

   public void Page_Load(object sender, EventArgs e)
   {
     if (!Request.Browser.SupportsCallback)
       throw new ApplicationException("This browser doesn't support " +
       "Client callbacks.");
     string src = Page.ClientScript.GetCallbackEventReference(this, 
       "arg", "DisplayResultsCallback", "ctx", 
	   "DisplayErrorCallback", false);        
     string mainSrc = @"function 
	 GetEmployeeDetailsUsingPostback(arg, ctx){ " +
	        src + "; }";
     Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
       "GetEmployeeDetailsUsingPostback", mainSrc, true);
   }
</script>
<html>
<head>
  <title>Retrieving XML Dynamically 
  using ASP.NET 2.0 Script Callback</title>
  <script language="javascript">    
    function GetEmployeeDetails()
    {
      var n = document.forms[0].txtEmployeeID.value;
      GetEmployeeDetailsUsingPostback(n, "txtNumber");
    }       
    
    function DisplayResultsCallback( result, context )
    {           
      var strXML,objXMLNode,objXMLDoc,objEmployee,strHTML;
      objXMLDoc = new ActiveXObject("Microsoft.XMLDOM");
      //Load the returned XML string into XMLDOM Object
      objXMLDoc.loadXML(result);
      //Get reference to the Employees Node
      objEmployee = objXMLDoc.selectSingleNode("EmployeesRoot").
        selectSingleNode("Employees");
      //Check if a valid employee 
	  //reference is returned from the server
      strHTML = "<font color='#0000FF'>";
      if (objEmployee != null)
      {
        //Dynamically generate HTML and append the contents
        strHTML += "<br><br>Employee ID :<b>" + 
          objEmployee.selectSingleNode("EmployeeID").text
		  	 + "</b><br><br>";
        strHTML += "Title:<b>" + 
          objEmployee.selectSingleNode("Title").text 
		  	+ "</b><br><br>";
        strHTML += "Hire Date :<b>" + 
          objEmployee.selectSingleNode("HireDate").text 
		  	+ "</b><br><br>";
        strHTML += "Gender:<b>" + 
          objEmployee.selectSingleNode("Gender").text 
		  	+ "</b><br><br>";
        strHTML += "Birth Date:<b>" + 
          objEmployee.selectSingleNode("BirthDate").text 
		  	+ "</b><br><br>";                
      }
      else
      {
        strHTML += "<br><br><b>Employee not found</b>";
      }
      strHTML += "</font>"
      //Assign the dynamically generated HTML into the div tag
      divContents.innerHTML = strHTML;            
    } 
    
    function DisplayErrorCallback( error, context )
    {
      alert("Employee Query Failed. " + error);
    }    
  </script>

</head>
<body>
  <form id="Form1" runat="server">    
    <font color="#800080"><H1>Employee Details</H1></font>
    <br><br>

    <P align="left">
      <font color="#800080"><b>Enter the Employee ID:</b>
      </font>     
      <INPUT id="txtEmployeeID" name="txtEmployeeID" 
        style="LEFT: 149px; TOP: 72px">
      <INPUT id="btnGetEmployee" type="button" 
	  	value="Get Employee Details" 
        name="btnGetEmployee" onclick="GetEmployeeDetails()">

    </P>
    <P></P>
    <div id="divContents">
    </div>
    <P></P>

  </form>
</body>
</html>

To understand the code better, consider the code listing as being made up of three different parts.

  • Implementing the server-side event for callback
  • Generating the client-side script for callback
  • Implementing client callback method

Start by looking at the server-side event for callback.

Implementing the Server-Side Event for Callback

At the top of page, you import the required namespaces by using the Import directive. After that we use the implements directive to implement the ICallbackEventHandler interface. This interface has a method named RaiseCallbackEvent that must be implemented to make the callback work.

<%@ implements interface="System.Web.UI.ICallbackEventHandler" %>

The signature of the RaiseCallbackEvent method is as follows.

void ICallbackEventHandler.RaiseCallbackEvent(string eventArgs)

As you can see from the above, the RaiseCallbackEvent method takes an argument of type string. If you need to pass values to the server-side method, you should use this string argument. Inside the RaiseCallbackEvent method, you store the supplied event argument in a local private variable for future use.

void ICallbackEventHandler.RaiseCallbackEvent(string eventArgument)
{
  _callbackArg = eventArgument;
}

After that, you override the GetCallbackResult() method as shown below.

string ICallbackEventHandler.GetCallbackResult()
{
  int value = Int32.Parse(_callbackArg);
  return GetEmployeeDetails(value);
}

The GetCallbackResult() method is the one that is responsible for returning the output of the server-side execution to the client. Inside the GetCallbackResult() method, you first convert the supplied employee ID into an integer type and then invoke a function named GetEmployeeDetails passing in the employee ID as an argument.

int value = Int32.Parse(eventArgs);
return GetEmployeeDetails(value);

As the name suggests, the GetEmployeeDetails() method retrieves the details of the employee and returns that information in the form of an XML string. This method starts by retrieving the connection string from the web.config file by using the following line of code.

string connString = WebConfigurationManager.
  ConnectionStrings["adventureWorks"].ConnectionString;

The above line of code retrieves the connection string from the connectionStrings section of the web.config file. The connection string is stored in the web.config as follows.

<connectionStrings>
  <add name="adventureWorks" 
    connectionString="server=localhost;Integrated Security=true;
    database=AdventureWorks"/>

</connectionStrings>

Once the connection string is retrieved, you then create an instance of the SqlConnection object passing in the connection string as an argument. Then you create instances of DataSet, SqlDataAdapter, and SqlCommand objects passing in the appropriate parameters to their constructors. Then you execute the sql query by invoking the Fill() method of the SqlDataAdapter object. Once the query is executed and the results available in the DataSet object, you then invoke the GetXml() method of the DataSet object to return the XML representation of the DataSet to the caller. The GetCallbackResult() method receives the output xml string and simply returns it back to the caller.

Generating the Client-Side Script for Callback

This section will look at the Page_Load event of the page. In the beginning of the Page_Load event, you check to see if the browser supports callback by examining the SupportsCallback property of the HttpBrowserCapabilities object.

if (!Request.Browser.SupportsCallback)
  throw new ApplicationException

Then you invoke the Page.ClientScript.GetCallbackEventReference() method to implement the callback in client-side. You can use this method to generate client-side code, which is required to initiate the asynchronous call to server.

string src = Page.ClientScript.GetCallbackEventReference(this, "arg",    
  "DisplayResultsCallback", "ctx", "DisplayErrorCallback", false);

The arguments passed to the GetCallbackEventReference method are as follows:

  • this — Control that implements ICallbackEventHandler(Current Page)
  • arg — String to be passed to server-side as argument
  • DisplayResultsCallback — Name of the client-side function that will receive the result from server-side event
  • ctx — String to be passed from one client-side function to other client-side function through context parameter
  • DisplayErrorCallback — Name of the client-side function that will be called if there is any error during the execution of the code
  • false — Indicates that the server-side function to be invoked asynchronously

When you execute this page from the browser and view the HTML source code, you will see that the following callback code is generated due to the above GetCallbackEventReference method call.

WebForm_DoCallback('__Page', arg, DisplayResultsCallback,
  ctx,DisplayErrorCallback, false)

WebForm_DoCallback is a JavaScript function (introduced with ASP.NET 2.0) that in turn invokes the XmlHttp class methods to actually perform the callback. Then you embed the callback code inside a function by concatenating the callback generated code with a JavaScript function named GetEmployeeDetailsUsingPostback using the following line of code.

string mainSrc = @"function " + 
  "GetEmployeeDetailsUsingPostback(arg, ctx)" + "{ " + src + "; }";

Finally you register the client script block through the Page.ClientScript.RegisterClientScriptBlock() method call. Note that in ASP.NET 2.0, the Page.RegisterClientScriptBlock and Page.RegisterStartupScript methods are obsolete. That’s why you had to take the help of Page.ClientScript to render client-side script to client browser. Page.ClientScript property returns an object of type ClientScriptManager type, which is used for managing client scripts.

Implementing Client Callback Method

In the client side, you have a method named GetEmployeeDetails, which is invoked when the Get Employee Details command button is clicked.

function GetEmployeeDetails()
{
  var n = document.forms[0].txtEmployeeID.value;
  GetEmployeeDetailsUsingPostback(n, "txtNumber");
}

From within the GetEmployeeDetails() method, you invoke a method named GetEmployeeDetailsUsingPostback() and pass in the required parameters. Note that the definition of the GetEmployeeDetailsUsingPostback() method is added in the Page_Load event in the server side (through the RegisterClientScriptBlock method call). Once the server-side function is executed, the callback manager automatically calls the DisplayResultsCallback() method.

The code of the DisplayResultsCallback() method is shown below. In this example, because the value returned from the server-side page is an XML string, you load the returned XML into an XMLDOM parser and then parse its contents.

objXMLDoc = new ActiveXObject("Microsoft.XMLDOM");

//Load the returned XML string into XMLDOM Object

objXMLDoc.loadXML(strXML);

Then you get reference to the Employees node by invoking the selectSingleNode method of the MSXML DOM object.


objEmployee = objXMLDoc.selectSingleNode("EmployeesRoot").
  selectSingleNode("Employees");

If a valid Employees element is returned from the function call, you display its contents. You display this information in a div tag by setting the innerHTML property of the div element to the dynamically constructed HTML.

if (objEmployee != null)
{
  //Dynamically generate HTML and append the contents
  strHTML += "<br><br>Employee ID :<b>" + 
    objEmployee.selectSingleNode("EmployeeID").text + "</b><br><br>";
  strHTML += "Title:<b>" + 
    objEmployee.selectSingleNode("Title").text + "</b><br><br>";
  strHTML += "Hire Date :<b>" + 
    objEmployee.selectSingleNode("HireDate").text + "</b><br><br>";
  strHTML += "Gender:<b>" + 
    objEmployee.selectSingleNode("Gender").text + "</b><br><br>";
  strHTML += "Birth Date:<b>" + 
    objEmployee.selectSingleNode("BirthDate").text + "</b><br><br>";	
}

When you browse to Listing 1 using the browser and search for an employee with employee ID of 1, the page will display the employee attributes such as title, hire date, gender, and birth date.

Figure 1

Figure 1

When you click on the Get Employee Details button in Figure 1, you will notice that the employee information is retrieved from the server and displayed in the browser; all without refreshing the page.

Conclusion

The callback capability in ASP.NET 2.0 is an extremely useful feature that can go a long way in increasing the usability of a Web application by obviating the need to perform post backs. You can implement callbacks to retrieve lookup data, perform validation, execute backend code, and so on. Callbacks can also play an important role in increasing the performance of a Web application by fetching only relevant data asynchronously. Microsoft’s “Atlas” tool for ASP.NET 2.0, currently available in pre-release format, provides libraries of ASP.NET Server Controls, Web services, and JavaScript libraries to simplify and enhance application creation by providing in-built controls and components that can be used in traditional JavaScript script and event or through ASP.NET Atlas script.

This article is adapted from Professional ASP.NET 2.0 XML by Thiru Thangarathinam (Wrox, 2006, ISBN: 0-7645-9677-2), from Chapter 9 “XML Data Display.” Copyright 2006 by Wiley Publishing, Inc. All rights reserved. Reproduced here by permission of the publisher.

Thiru works for Intel Corporation where he focuses on enterprise applications and service oriented architecture. He is a Microsoft Certified Application Developer (MCAD) specializing in architecting and building distributed n-tier applications using ASP.NET, C#, VB, ADO.NET, and SQL Server..

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories