Practical ASP.NET

Integrate the Client and Server

Take advantage of a new tool for integrating client-side activities with server-side resources in ASP.NET 2.0 by exploiting the ability to have client-side code call server-side routines.

Technology Toolbox: VB.NET, ASP.NET, ASP.NET 2.0

ASP.NET 2.0 integrates client-side activities with server-side resources so your client-side code can call server-side routines.

p> One of the fundamental problems in providing a responsive user interface in Web applications is the great divide between code that executes in the browser and accessing server-side resources. Client-side developers get around this problem by approaching it from several angles—including duping arrays of database information into the page's client-side code or storing data in hidden fields. But these approaches, even when they produce the desired end result, are workarounds, at best.

The ASP.NET 2.0 client callback solution for accessing server-side resources from the client is not only simpler, but more secure (because you don't have to add data to the page's source). It's a more elegant solution all the way around.

The callback solution can prove a powerful technique, but you should be aware that using this functionality requires that you take advantage of ASP.NET's ability to add client-side code to your Web page dynamically.

One of the most attractive features of ASP.NET 2.0 client callbacks is that they work on most modern browsers, including Internet Explorer 6 or later, Firefox, Netscape Navigator 1 or later, and Safari. It so happens that Internet Explorer is the least attractive platform for implementing client callbacks because the implementation is supported by native functionality in non-Microsoft browsers, but requires the XMLHTTP ActiveX control to work in Internet Explorer.

A client callback begins with boilerplate client-side code in the browser that calls a function (called RaiseCallbackEvent) in your server-side code, passing the value of one variable from the client to the server. You can insert any code you want into the RaiseCallbackEvent function, as long as you return a value, which is passed back to a client-side routine in the browser (see Figure 1). Nothing happens from the user's perspective during the callback process; the page isn't posted back, and the page's HTML isn't reprocessed or redisplayed. In fact, you can even make the call to the server asynchronously, so the user can continue to work with the page during the callback. From your point of view, you have seamless access from client-side code to server-side resources whenever you need them.

Let's look at a callback implementation in action. Assume you have a scenario where a page for processing sales orders accesses the server only once, to validate a customer ID entered into a textbox on the page. You need to add a variable to the page to hold the customer ID, as well as add some client-side code to ensure your variable is set to the customer ID. The script in your page should include a routine you call when the user leaves the CustomerId textbox to update a variable (custid, in this example) with the value of the textbox:

<script>
var custid;
function CustomerNumber_onBlur() {
custid = document.all.txtCustomerId;
}
<script>

Your tag for the textbox to call the routine looks like this:

<input type="text" id="txtCustomerId" 
   onblur="CustomerId_onBlur()" ?

You could simply embed this code into the Web page. However, you need to be cognizant of browser-compatibility concerns when working with client-side code—not all code executes in all browsers. You should add the code dynamically from your server-side code in the PreRender event. This lets you tailor the code for different browsers, should you discover browser incompatibilities during testing.

Insert the Client-Side Script
Place this code in the page's PreRender method to insert the client-side script into the page, then set the textbox's onblur event to call the client-side routine:

Dim csm As Web.UI.ClientScriptManager
Dim strCodeString As String

Dim strCodeString As String = "var custid;" & _
   "function CustomerId_onBlur() {" & _
   "custid = document.all.txtCustomerId.value;}" 

csm = Me.Page.ClientScript
csm.RegisterClientScriptBlock(Me.GetType, _
   "loadCustid", strLoadData, True)

Me. txtCustomerId.Attributes("onblur") = _
   "CustomerId_onBlur();"

ASP.NET 1.0 included a RegisterClientScriptBlock method, but Microsoft introduces several changes to the method in version 2.0. First, the method moves from the ASP.NET 2.0 Page object to the ClientScriptManager object—the ClientScriptManager provides one-stop support for client-scripting support. You retrieve the ClientScriptManager from the page's ClientScript property.

The RegisterScriptBlock method also acquires a new first parameter (the type of the object that the block is associated with) and a new last parameter (when set to True, this parameter causes the code to be wrapped inside of <script> tags). The second and third parameters remain from the ASP.NET 1.0 version of the RegisterScriptBlock method. The second parameter assigns a key to the script block to let you check whether the block has been added, and the third parameter holds the client-side code you want inserted into the page.

The next step in handling a client callback is to create the client-side routine that will be called after you run your server-side code. The routine must accept a single parameter, which you set to the value returned from your server-side routine.

For example, this client-side code checks the parameter passed to it and displays a message if the parameter is False. Use this code to add this routine to the client:

Dim strCallBack As String = _
   "function ReportCustId(IsCustValid) {" & _
   " if (isCustValid == false) {" & _
   "window.alert('Invalid customer number');}}" 

csm.RegisterClientScriptBlock(Me.GetType, "callback", _
   strCallBack, True)

You now have most of the client-side code in place, so you can turn your attention to writing the server-side code that accepts the client-side value and returns a value to your client-side routine. Your page must implement the System.Web.UI.ICallbackEventHandler interface and add a subroutine that implements RaiseCallbackEvent from that interface. This routine accepts a string parameter that is the value of the variable passed from your client-side code. You also must create a function that implements the GetCallbackResult method. This function returns a string value that is returned to the client-side code. ASP.NET takes care of calling the GetCallbackResult method after your RaiseCallbackEvent method executes.

In this example, the function that implements RaiseCallbackEvent is called CatchCustId. The routine accepts the customer ID and updates a class-level variable called strTestCustId. The TestCustId function in this example implements the GetCallbackResult function and returns a string result of either "true" or "false" depending on the result of calling a function (not shown here) that validates the customer ID:

Partial Class SalesOrder
	Inherits System.Web.UI.Page
	Implements System.Web.UI.ICallbackEventHandler

Dim strTestCustId As String

Public Sub CatchCustId(ByVal CustId As String) _
	Implements _
	System.Web.UI.ICallbackEventHandler. _
	RaiseCallbackEvent

	strTestCustId = CustId

End Function

Protected Function TestCustId() As String _
	Implements _
	System.Web.UI.ICallbackEventHandler. _
	GetCallbackResult

	If  ValidateCustId(CustId) = True Then
		Return "true"
	Else
		Return "false"
	End If

End Function

ASP.NET runs your server-side code at run time, passing the client-side variable. After your server-side code finishes running, you return a value from your server-side function, which is passed to your client-side routine, which then executes.

Wire Up Server and Client Processing
All that remains is to tell ASP.NET which client-side variable to pass, tell ASP.NET which client-side routine to call, and decide what will trigger this process.

Some JavaScript client-side code handles the connection between your client-side variable, your server-side code, and your client-side routine. Much of that code is general-purpose code that is installed with the .NET Framework. You call the ClientScriptManager's GetCallbackEventReference method to generate the code that specifically references your client-side routine and variable, which you can then insert into your page. You must pass this method a reference to your page, as well as the name of the client-side variable holding the customer ID, the name of the client-side routine you want to call after the server-side code executes, and a fourth string parameter that specifies a second parameter for the client-side routine (which you'll learn about later in the article). You need to decide when you want to call your server-side routine before inserting this line of code.

For the sake of demonstrating another routine for inserting client-side code into the page, let's tie the code to the page's onsubmit event by using the ClientScriptManager's RegisterClientOnSubmitStatement method. This method accepts three parameters (a reference to the custom control, a key to identify the code block, and the code), but runs the associated client-side code when the page is submitted:

Dim strCodeString As String
Dim csm As System.Web.UI.ClientScriptManager

strCodeString = csm.GetCallbackEventReference( _
   Me, "custid", "ReportCustId", "null")
csm = Me.Page.ClientScript

csm.RegisterClientOnSubmitBlock( _
   Me, "ValidateClientId", strCodeString)

You have several options available to you when implementing client callbacks. One approach is to pass two parameters to the client-side routine called after the server-side code. Modifying the original client-side code to accept a second parameter gives you a client-side routine like this:

function ReportCustId(IsCustValid, ProcessOption) {

You can specify what is passed as the second parameter to the client-side routine by setting the fourth parameter of the GetCallbackEventReference method. If the value in the context parameter is the name of a client-side variable, you will pass the value of that variable to the client-side routine. If the context parameter's value isn't the name of a client-side value, then you will pass the value of the context parameter to the client-side code. You can use this approach to pass context information that modifies how the client-side routine processes the result from the server.

For example, assume you need to pass the value "FailOnError" to the second parameter in the client-side code. This code passes FailOnError as the second parameter to the client-side code by specifying the value as the fourth parameter to GetCallbackEventReference. Assuming there is no client-side variable called FailOnError, the client-side code receives the value FailOnError. The client-side code might use this information to decide how to respond when an invalid customer ID is processed:

strCodeString = csm.GetCallbackEventReference( _
   Me, "custid", "ReportCustId", "FailonError")

You can execute additional functionality within the code that calls your sever-side routine by appending the additional code to the end of the string returned by the GetCallbackEventReference. This code calls the InitializeCountries routine after calling the server-side code:

btn.Attributes.Add("onclick", strCodeString _
   & "; InitializeCountries();")

Use Asynchronous Processing
A call to the Web server can be time-consuming, so you might want your client-side processing to continue while the call is being made to the server. Turn on asynchronous processing by passing True as the fifth parameter to the GetCallbackReference method:

strCodeString = csm.GetCallbackEventReference( _
   Me, "custid", "ReportCustId", "null")

Take another look at the example that calls the InitializeCountries routine after calling the server-side code. Setting asynchronous processing to True causes the InitializeCountries routine to run while waiting for the call to the server-side code to return. If you don't turn on the asynchronous processing option, the browser-side code pauses after calling the server-side code. In fact, it won't continue on to the InitializeCountries routine until after the server-side code finishes processing (and the client-side routine finishes running).

If the server-side code fails, you can notify your client-side code of the failure by calling another client-side routine. Implement this client-side error handler by passing the name of the routine you want to call after a server-side error as the fifth parameter to the GetCallbackEventReference method (the asynchronous processing parameter becomes the sixth parameter if you use this option). This example inserts a client-side routine called ValidationFailed; call this in the event of server-side failure:

Dim strErrorCallBack As String = _
   "function ValidationFailed() {" & _
   "window.alert('Unable to Validate Customer Id');}"
csm.RegisterClientScriptBlock( _
   Me.GetType, "callbackerror", strErrorCallBack, True)

strCodeString = csm.GetCallbackEventReference( _
   Me, "custid", "ReportCustId", "null")

Note that your page is requested just as it would be for any other page request when the server-side callback code executes. This means that all of the standard events for your page—including Load, Unload, PreRender, and so on—execute. It might be that you have code in these events that you don't want to run during a callback. You can check to see whether your control is being processed because of a callback by checking the page's IsCallback property, which is set to True during callback processing (the IsPostBack property is also set to True during callback processing).

The example callback ties the callback to the page's submit event, so you call the page twice: first for the callback and, immediately afterwards (assuming that the customer ID is valid), for the postback. During the first call, IsPostBack and IsCallback are set to True; during the second call, only IsPostBack is set to True.

Most browsers support client callback, but you should still check for that support before inserting your callback code. You can determine if a browser supports callbacks by checking the SupportsXmlHttp and SupportsCallback properties of the BrowserCapabilities object. You can retrieve the BrowserCapabilities object from the Browser property of the Request object. The SupportsCallback property indicates whether the browser supports client callbacks, while the SupportXMLHttp property indicates whether the browser supports sending XML over HTTP (the communication method used by the client callback system).

SupportXMLHttp and SupportsCallback return the same value at this time. But it's conceivable that Microsoft might use a mechanism other than XMLHttp to implement client callbacks in a later version of ASP.NET. If that occurs, the return value from these two properties would probably differ.

Checking whether a browser supports client callbacks before inserting the client callback code takes only a handful of lines of code:

If Me.Page.Request.Browser.SupportsXmlHttp = _
   True Then
   strCodeString = csm.GetCallbackEventReference( _
      Me, "custid", "ReportCustId",  "null")
   btn.Attributes.Add("onclick", strCodeString & ";")
End If

This article's example ties only a single client-side activity to a server-side resource, but you can extend client callbacks to handle as many operations as you want. Your page passes only a single variable to the server-side routine, so you need to ensure that the necessary data is loaded into the variable at the moment you call the server-side code. Also, you can call only one single server-side routine with your control, so you also need to ensure that data passed in the parameter to that server-side routine signals what action you want taken on the server. Offsetting those caveats is this: You can pass any data that you can fit into a string that is passed to the server (including serialized objects). Your options are limited only by the amount of coordination code you're willing to write.

comments powered by Disqus

Featured

Subscribe on YouTube