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. TheRaiseCallbackEvent
method stores the client-side arguments in a private variable for later use. After that, the ASP.NET invokes another method namedGetCallbackResult()
, 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 implementsICallbackEventHandler(Current Page)
arg
— String to be passed to server-side as argumentDisplayResultsCallback
— Name of the client-side function that will receive the result from server-side eventctx
— String to be passed from one client-side function to other client-side function through context parameterDisplayErrorCallback
— Name of the client-side function that will be called if there is any error during the execution of the codefalse
— 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
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..