Microsoft & .NETASPASP.NET 2.0 Client-Side Features: Bring Server-Side Power to the Client

ASP.NET 2.0 Client-Side Features: Bring Server-Side Power to the Client

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

In addition to providing a number of new features on the server side, ASP.NET 2.0 also provides a number of improvements on the client side; these increase Web developers’ productivity. These feature improvements include script callbacks, cross-page postbacks, setting focus in a page, client-side button click events, and maintaining scrollbar position in the page. This article explores ASP.NET 2.0’s client-side features in detail, providing examples of how to use them in your Web applications.

Script Callback

Probably the most interesting ASP.NET 2.0 client-side feature is script callback, which allows you to make a server-side call from the client side without posting back to the server. Think of all the times when you wanted to retrieve information from the server after the user had performed some action, like entering a product number. To accomplish this, you traditionally posted the current page to the server, retrieved the product information, and refreshed the page with the information retrieved from the server. Although very common, this method is inefficient: the Web page refreshes, re-rendering an entire page of content, even though only a small percentage of the page has actually changed. You probably have encountered this inefficiency when searching a catalog or a search engine. The delays and waste of resources can be significant.

However, if you could accomplish the same functionality without refreshing the browser page, you would greatly enhance the user experience. This is precisely where script callback comes into play. It provides a simple and straightforward way to execute remote logic directly from the client side.

Script Callback Architecture

Script callback provides a way to execute a piece of code on the server without leaving the current page or posting back to the server. Before looking at an example, take a look at the architecture of the script callback. Figure 1 shows how script callback works.

Figure 1. How Script Callback Works

Performing a client callback involves the following steps:

  1. Call the page’s GetCallbackEventReference method to get the name of a client-side function that will be called to initiate a callback. (Note that the GetCallbackEventReference method is declared in the ICallbackEventHandler namespace and you must explicitly implement that interface by adding the Implements directive at the top of the page.) Then, that function is called from the client side (obviously using a client-side script).
  2. That function prompts ASP.NET’s callback manager, which is itself implemented in client-side script, to launch an XMLHTTP callback to the server.
  3. On the server side, ASP.NET receives the call and invokes the page’s ICallbackEventHandler.RaiseCallbackEvent() method, which processes and returns the callback.
  4. The callback manager is notified that the execution has completed.
  5. The callback manager notifies the caller that the call completed by calling a client-side notification function (provided by the caller when it initiated the call).

To demonstrate the script callback feature, this article provides an example wherein you retrieve the details of a product based on the product ID entered in the Web page. (To download the accompanying source code for the example, click here.) You will learn how to retrieve the product details without posting the page back to the server.

Implement Script Callback in an ASP.NET 2.0 Page

Start by creating a Web site named ClientFeatures. Next, add a new Web form named ScriptCallback.aspx to it. Modify the code in the ScriptCallback.aspx file to read as follows:

<%@ 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">

public string RaiseCallbackEvent(string eventArgs)
{
   try
   {
      int value = Int32.Parse(eventArgs);
      return GetProductDetails(value);
   }
   catch (Exception ex)
   {
      throw new ApplicationException
         ("An Error has occured during the processing " +
          " of your request. Error is :" + ex.Message);
   }
}

public string GetProductDetails(int productID)
{
   string connString =
      WebConfigurationManager.ConnectionStrings["Northwind"].
      ConnectionString;
   using (SqlConnection sqlConnection = new SqlConnection(connString))
   {
      try
      {
         DataSet productsDataset = new DataSet("ProductsRoot");
         //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 ProductID,ProductName,QuantityPerUnit, " +
         "UnitPrice from Products Where ProductID =" +
         productID.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(productsDataset,"Products" );
         return productsDataset.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 GetProductDetailsUsingPostback(arg, ctx)
      { " + src + "; }";
   Page.ClientScript.RegisterClientScriptBlock(this.GetType(),
      "GetProductDetailsUsingPostback", mainSrc, true);
}

</script>
<html>
<head id="Head1" runat="server">
<title>
   Retrieving XML Dynamically using ASP.NET 2.0 Script Callback
</title>
<script language="javascript">

function GetProductDetails()
{
   var n = document.forms[0].txtProductID.value;
   GetProductDetailsUsingPostback(n, "txtNumber");
}

function DisplayResultsCallback( result, context )
{
   var strXML,objXMLNode,objXMLDoc,objProduct,strHTML;
   objXMLDoc = new ActiveXObject("Microsoft.XMLDOM");
   //Load the returned XML string into XMLDOM Object
   objXMLDoc.loadXML(result);
   //Get reference to the Products Node
   objProduct = objXMLDoc.selectSingleNode("ProductsRoot").
   selectSingleNode("Products");
   //Check if a valid product reference is returned from the server
   strHTML = "<font color='#0000FF'>";
   if (objProduct != null)
   {
      //Dynamically generate HTML and append the contents to a
      //string variable
      strHTML += "<br><br>Product ID :<b>" +
         objProduct.selectSingleNode("ProductID").text +
         "</b><br><br>";
      strHTML += "Product Name :<b>" +
         objProduct.selectSingleNode("ProductName").text +
         "</b><br><br>";
      strHTML += "Quantity Per Unit :<b>" +
         objProduct.selectSingleNode("QuantityPerUnit").text +
         "</b><br><br>";
      strHTML += "Unit Price :<b>" +
         objProduct.selectSingleNode("UnitPrice").text +
         "</b><br><br>";
   }
   else
   {
      strHTML += "<br><br><b>Product not found</b>";
   }
   strHTML += "</font>"
   //Assign the dynamically generated HTML into the div tag
   divContents.innerHTML = strHTML;
}

function DisplayErrorCallback( error, context )
{
   alert("Product Query Failed. " + error);
}
</script>
</head>
<body>
<form id="Form1" runat="server">
<font color="#800080"><H1>Product Details</H1></font>
<br><br>
<P align="left"><font color="#800080">
<b>Enter the Product ID:</b></font>
     
<INPUT id="txtProductID" name="txtProductID"
       style="LEFT: 149px; TOP: 72px">
<INPUT id="btnGetProduct" type="button" value="Get Product Details"
       name="btnGetProduct" onclick="GetProductDetails()">
</P><P></P>
<div id="divContents">
</div>
<P></P>
</form>
</body>
</html>

Let’s walk through the preceding lines of code. At the top of page, you import the required namespaces by using the Import directive. After that, you use the implements directive to implement the ICallbackEventHandler interface:

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

The ICallbackEventHandler interface has a method named RaiseCallbackEvent that must be implemented to make the callback work. The signature of the RaiseCallbackEvent method is as follows:

public string RaiseCallbackEvent(string eventArgs)

It 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 first convert the supplied product ID into an integer type and then invoke a function named GetProductDetails passing in the product ID as an argument:

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

As the name suggests, the GetProductDetails method simply retrieves the details of the product 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:

string connString = WebConfigurationManager.ConnectionStrings
                    ["Northwind"].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="Northwind"
      connectionString="server=localhost;integrated security=true;
      database=Northwind"/>
</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, it creates instances of DataSet, SqlDataAdapter, and SqlCommand objects passing in the appropriate parameters to their constructors. Next, it executes the SQL query by invoking the fill method of the SqlDataAdapter object. Once the query is executed and the results are 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 RaiseCallbackEvent method receives the returned XML string and simply returns it back to the caller.

Now, switch your focus to the Page_Load event of the page. In the beginning of the Page_Load event, you check to see whether the browser supports callback by examining the SupportsCallback property of the HttpBrowserCapabilities object:

if (!Request.Browser.SupportsCallback)
   throw new ApplicationException
   ("This browser doesn't support " +
    "Client callbacks.");

Then, you invoke the Page.ClientScript.GetCallbackEventReference method to implement the callback on the client side. You use this method to generate client-side code, which is required to instantiate the asynchronous call to the server:

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

The GetCallbackEventReference method accepts the following arguments:

  • this—A control that implements ICallbackEventHandler(Current Page)
  • arg—A string you pass to the server side as an argument
  • DisplayResultsCallback—The client-side function that will receive the result from a server-side event
  • ctx—A string you pass from one client-side function to another through a context parameter
  • DisplayErrorCallback—The client-side function that will be called if an error occurs during this process
  • false—A Boolean to indicate if an asynchronous approach should be used to invoke the function

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

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

WebForm_DoCallback is a JavaScript function 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 GetProductDetailsUsingPostback using the following line of code:

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

Finally, you register the client script block through the Page.ClientScript.RegisterClientScriptBlock method call. Note that the Page.ClientScript property returns an object of type ClientScriptManager, which is used for managing client scripts. On the client side, you have a method named GetProductDetails, which is invoked when the “Get Product Details” command button is clicked:

       function GetProductDetails()
       {
          var n = document.forms[0].txtProductID.value;
          GetProductDetailsUsingPostback(n, "txtNumber");
       }

From within the GetProductDetails method, you invoke a method named GetProductDetailsUsingPostback and pass in the required parameters. Note that the definition of the GetProductDetailsUsingPostback method is added in the Page_Load event on the server side (through the RegisterClientScriptBlock method call).

Once the server-side function is executed, the callback manager automatically calls the DisplayResultsCallback method. Inside the DisplayResultsCallback method, you load the returned XML into an XMLDOM parser and then display its contents.

When you browse to the ScriptCallback.aspx file and search for a product with product ID of 1, the output should be somewhat similar to Figure 2.

Figure 2. Search Result for Product with ID of 1

When you click on the “Get Product Details” button in Figure 2, the product information is retrieved from the server and displayed in the browser—all without refreshing the page.

As you can see, the callback capability in ASP.NET 2.0 is an extremely useful feature that can go a long way toward 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 also can play an important role in increasing the performance of a Web application by asynchronously fetching only relevant data.

Cross Page Postback

Prior to ASP.NET 2.0, you could perform postbacks only to the same page. Now, ASP.NET 2.0 offers a new feature that allows you to post back to another ASP.NET page. You utilize this feature by setting the PostBackUrl property of a button control to the page that will handle the postback. Once in the second page, you can access the controls from the previous page by accessing the Page.PreviousPage property.

In the following code sample identified by the file PageOne.aspx, you have a button control that has its postbackurl property set to PageTwo.aspx. The page also contains a text box named Input:

<%@ Page Language="C#" %>

<html>
   <body>
      <form id="Form1" runat="server">
         <asp:TextBox ID="Input" RunAt="server" />
         <asp:Button ID="Button1" Text="Post to PageTwo"
                     PostBackUrl="PageTwo.aspx" RunAt="server" />
      </form>
   </body>
</html>

The code of the PageTwo.aspx file looks like the following:

<%@ Page Language="C#" %>

<script runat="server">
void Page_Load (Object sender, EventArgs e)
{
   if (Page.PreviousPage != null &&
       Page.PreviousPage.IsCrossPagePostBack)
   {
      TextBox input=(TextBox) Page.PreviousPage.FindControl("Input");
      Output.Text = "Hello, " + input.Text;
   }
}
</script>

<html>
   <body>
      <asp:Label ID="Output" RunAt="server" />
   </body>
</html>

Inside the Page_Load event, you check the Page.PreviousPage property to see whether the user has arrived to this page using cross postback. Once that check is done, you then reference the textbox control in PageOne.aspx file using the FindControl method of the Page.PreviousPage property. You then typecast the return value into a TextBox control and then retrieve its text value for display in the label control.

Reference Controls Using Strongly Typed Approach

In the previous example, you referenced the control from the PageOne.aspx file using a weakly typed approach, which requires you to typecast the return value of the FindControl method into a text box control. Although this approach works for most scenarios, sometimes you will want to reference the controls in the PageOne.aspx using a strongly typed approach. To do that, take the following steps:

  1. Expose the appropriate controls or their properties in the form of public properties from PageOne.aspx.
  2. In PageTwo.aspx, set the PreviousPageType directive at the top of the page. The declaration of the PreviousPageType directive is shown below:
  3. <%@ PreviousPageType VirtualPath="~/PageOne.aspx" %>

Once you set the PreviousPageType directive, you have strongly typed access to all the public properties in PageOne.aspx from PageTwo.aspx.

Setting Focus

In ASP.NET 1.x, setting focus to a control required some client-side script that would use JavaScript functions to assign focus to a control. With the release of ASP.NET 2.0, you now have the option of setting focus to appropriate controls on the page. You can accomplish this using either of the following techniques:

  • Invoke the Page.SetFocus() method passing in the name of the control.
  • Invoke the Focus() method of the actual control itself.

The following code example shows how to set the focus of the page to a TextBox control when the page is loaded in the browser:

<%@ Page Language="C#" %>

<script runat="server">
void Page_Load (Object sender, EventArgs e)
{
   Page.SetFocus ("UserName");
}
</script>

<html>
   <body>
      <form id="Form1" runat="server">
         <asp:TextBox ID="UserName" RunAt="server" />
      </form>
   </body>
</html>

Similar to setting page focus, you can also set multiple default buttons on a page. The next section shows how to accomplish this.

Set Multiple Default Buttons

As you might remember, one of the limitations of an ASP.NET page is that you can have only one form element with a runat=server attribute set in the page. To avoid this limitation, ASP.NET 2.0 provides a way to set multiple default buttons for a panel control. Using this feature, you can create multiple default buttons (one for each section of the page that is identified by a panel control), enabling a different default button depending on the panel control that has the current focus.

The following simple ASP.NET page shows how to accomplish this:

<%@ Page Language="C#" %>

<html>
   <body>
      <form id="Form2" runat="server">
         <asp:Panel ID="Panel1" DefaultButton="Button1"
                    RunAt="server">
            <asp:Button ID="Button1" RunAt="server"/>
         </asp:Panel>
         <asp:Panel ID="Panel2" DefaultButton="Button2"
                    RunAt="server">
           <asp:Button ID="Button2" RunAt="server"/>
         </asp:Panel>
      </form>
   </body>
</html>

To emulate default button and default focus behaviors for the overall form, you also can set the DefaultButton and DefaultFocus properties of the Form element to the ID value of the specific controls.

Handle Client-Side Click Events

In ASP.NET 2.0, the button control provides a new event called OnClientClick that you can use to execute some client-side script before posting back to the server. This approach provides a way to programmatically run client-side script when the button is clicked:

<%@ Page Language="C#" %>
<script runat="server">
   void Button1_Click(Object sender, System.EventArgs e)
   {
      Response.Write("Server-side click event fired too");
   }

</script>

<html>
<head>
<script language="javascript">
   function OnClientClickEvent()
   {
      alert('Client side click event fired');
      //To cancel the server side event, simply return false
      //from this method
   }

</script>
</head>
   <body>
      <form id="Form1" runat="server">
         <asp:Button ID="Button1"
                     OnClientClick="return OnClientClickEvent()"
                     Text="Click Me!" runat="server"
                     OnClick="Button1_Click"/>
      </form>
   </body>
</html>

As the code shows, the Button control has an event handler named OnClientClickEvent that displays an alert message when the button is clicked. From this function, you also can cancel the server-side event by returning false.

Maintaining Scrollbar Position

ASP.NET 1.x had a property named SmartNavigation that enhanced the user’s experience of the page by eliminating the flash caused by navigation. ASP.NET 2.0 replaces the SmartNavigation property with the MaintainScrollPositionOnPostBack property.

Similar to SmartNavigation, you set this new property in the @ Page directive in the .aspx file or in the web.config file. When a user requests the page, the dynamically generated class sets this property.

Here is how you set this property at the page level:

<%@ Page Language="C#" MaintainScrollPositionOnPostBack="true" %>

To set this property for all the pages in a Web application, use the following entry:

<pages maintainScrollPositionOnPostBack="true"/>

Server-Side Power on the Client Side

The new client-side features introduced with ASP.NET 2.0 complement the powerful server-side programming model by exposing some of the server-side capabilities on the client side. The new client-side features, along with the new server-side features, make ASP.NET a powerful tool in a Web developer’s arsenal.

Download the Code

To download the accompanying source code for the examples, click here.

About the Author

Thiru Thangarathinam has six years of experience in architecting, designing, developing, and implementing applications using object-oriented application development methodologies. He also possesses a thorough understanding of the software life cycle (design, development, and testing). He holds several certifications, including MCAD for .NET, MCSD, and MCP. Thiru is an expert with ASP.NET, .NET Framework, Visual C# .NET, Visual Basic .NET, ADO.NET, XML Web services, and .NET Remoting. Thiru also has authored numerous books and articles. Contact him at thiruthangarathinam@yahoo.com.

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories