Introduction

After all the fuss and the hype about AJAX technologies, now there are plenty of solutions for AJAX that developers can choose from. The drawback is that you have to replace the standard ASP.NET controls that your web pages contain with custom AJAX-enabled controls, and/or write JavaScript code for the client to process the data returned by the server.

What if there was a "magical" panel control that, when you drag-n-drop ordinary, old-fashioned, ASP.NET controls, "magically" transforms them to AJAX-enabled controls? Impossible! Well, this article claims that such a panel can exist (AjaxPanel) and sadly, there's no magic about it; it's all plain C# code.

Update - 12 Nov 2005: The MagicAjax framework is now hosted on SourceForge. Many improvements and features have been added since the initial release, including support for ASP.NET 2.0.

Background

This article assumes that you know what AJAX is. If that's not the case, there are plenty of good articles in CodeProject to get you started. The code behind this article is based on the excellent series AJAX WAS Here by Bill Pierce. Understanding the client JavaScript framework and how it invokes server methods is not required to use the classes in this article, but if you want to see what happens "under the hood", I strongly recommend reading Bill Pierce's articles.

Using the code

I have included a demo project to show you how to convert the existing plain postback controls to AJAX-like ones.

BubisChat.aspx

This page tries to be a chat application. I will not get into the details of how it works; it was created just for demonstration purposes. It uses the standard ASP.NET controls, no mystery here. The buttons cause a postback to the server and the page reloads and fills the controls with the new data.

AjaxBubisChat.aspx

This page uses the same controls but it works quite differently. The controls are refreshed instantly and without any reloading by the browser. That's right, AJAX is here.

Hey, how did you do that

There are four steps required to convert BubisChat.aspx to AjaxBubisChat.aspx (or to apply AJAX to any page using this framework):

Make the page inherit from AjaxPage (optional)

publicclass AjaxBubisChat : Ajax.AjaxPage

Inheriting from AjaxPage is not required; it just handles the callback event and provides some useful properties for convenience. To handle the callback event in a page that inherits from AjaxPage, you override the OnCallBack method:

If the page doesn't inherit from AjaxPage, it can handle the callback event by implementing the ICallBackEventHandler interface:

publicinterface ICallBackEventHandler
{
void RaiseCallBackEvent();
}

The callback is like a postback but without the reloading of the page by the browser. I'll explain what the callback does a little later. For reasons that will become clear later, the Load event of the page and its controls are not raised during a callback. You must use the callback event instead.

During a callback, the HttpContext of the page is invalid, so you can't use the Request/Response properties of System.Web.UI.Page, you have to use the CallBackHelper.Request and CallBackHelper.Response properties instead. The AjaxPage provides valid Request/Response properties so that you don't have to replace them in your code for the ones from CallBackHelper.

Put inside an AjaxPanel the controls that will get refreshed without a postback

It can be one AjaxPanel for each TextBox and ListBox, or one AjaxPanel for all of them. The buttons should be inside an AjaxPanel, so that their submit function is replaced automatically for a callback function. In this example, I just put all the controls inside one AjaxPanel for convenience.

Configure the web.config file

<!-- If CallBackScriptPath is not set in the appSettings,
"/ajax/script" is used--><addkey="CallBackScriptPath"value="/ajax/script"/>

at the <appSettings> section of the web.config file.

The AjaxHttpModule processes the callback at the AcquireRequestState event of the HttpApplication, after the request has been authenticated. The default script path is valid if you extract the source files to the wwwroot path. If you extract them to another directory, you should change the CallBackScriptPath of the application settings accordingly.

Enable the CallBackTimer on the page (optional)

This is required so that the chat textbox is automatically refreshed if there are new messages to display. Most pages don't need automatic refresh, so just ignore this if that's the case.

And believe it or not, that was all! No JavaScript and no replacement of the controls were necessary. You can add new controls to the AjaxPanel either by code or by using the Visual Studio Designer.

So, what does a callback do

The job of the callback is to invoke server-side control events (and a special CallBackTimer event if it is enabled). For more details, I suggest reading Bill Pierce's articles that I have mentioned in the Background section. When the server receives a callback, it returns generic JavaScript code. The client doesn't care what the JavaScript does (populating a ListBox, invoking an alert box, whatever), it just executes them. Thus, contrary to the usual mentality of AJAX applications, it's up to the server to manipulate the page using JavaScript, not the client. That way, instead of trying to embed JavaScript code in the web page and synchronize it with the server code, the focus is shifted on implementing all the functionality of a custom control on the server side without having to separate the code to the JavaScript part, and to the C# (VB.NET, whatever) part.

Now, it is getting more interesting... A page that is AJAX-enabled is stored as a session state variable. When a callback is invoked, the AjaxHttpModule intercepts it, finds the originating page from the session and invokes the appropriate events for the controls of the page without reloading the original page.

Why store the page in the session

There is no overhead because of constant reloading of the page and its controls.

The illusion of AJAX is that the page behaves like a desktop application. In a desktop application the controls are kept in memory without reloading them every time a button is pressed, and the controls can be added or removed dynamically. Well, by persisting the page in the session we don't have to reload the controls and the controls can be added or removed dynamically; and if you add them to the AjaxPanel they will actually appear on the browser too!

In order for a page to be stored in the session, it must contain at least one AjaxControl control (base class of AjaxPanel). The session key that is used is the URL of the originating page so that different pages can be distinguished from one another.

The callback doesn't have to come from a control contained inside an AjaxPanel, it can be invoked from any control on the page, as long as it is properly configured to call the appropriate callback function, like this:

The CallBackHelper.GetCallbackEventReference method provides the AJAXCbo.DoPostCallBack call on the client side, and return false; is added so that the OnClick event can override the submit function.

The AjaxPanel, by default, automatically configures all the submit buttons that it contains to call the callback function and replaces the __doPostBack calls of normal postback with a AJAXCbo.DoPostCallBack call. If you want to manually set the OnClick event of your controls, set the SetCallBackForSubmitButtons and SetCallBackForChildPostBack properties of AjaxPanel to false.

The mysterious AjaxPanel

The task of AjaxPanel is to reflect its contents on the browser of the client each time a callback is invoked. To accomplish this, it scans the controls that it contains and produces the appropriate JavaScript code for every control that is added, removed, or altered, ignoring the controls that haven't changed at all. In order to spot changes, it renders each control and checks if the produced HTML is different from the one obtained during the previous callback.

In addition, if the AjaxPanel encounters any RenderedByScriptControl controls (AjaxPanel inherits from this class), it ignores them and lets them take care of their "reflection" to the browser. Thus, if an AjaxPanel (parent) contains another AjaxPanel (child), and a control of the child-AjaxPanel is altered, the parent-AjaxPanel won't send the entire HTML rendering of the child-AjaxPanel, but instead the child-AjaxPanel will send just the HTML of the altered control. That way, the size of the JavaScript code that the client gets as a response of a callback, is greatly reduced.

Limitations

The known limitations are:

You cannot remove an attribute of the AjaxPanel control. The removal will not be reflected on the page. Use:

ajaxPanel.Attributes["attrib"] = "";

instead.

The controls contained inside an AjaxPanel doesn't invoke client-side page validation by default. You have to "manually" insert the appropriate JavaScript in the OnClick event of the control.

Tested only in Internet Explorer and FireFox. There is no alternative to XmlHttp if it is not available for the browser.

Session must be in "InProc" mode. "SQLServer" and "StateServer" modes are not supported.

To sum up

I'm not going to get into the details of the inner workings of the classes. I have tried to document every method, so anyone wanting to extend the AJAX controls that are provided, I strongly recommend reading the comments of all the methods and the classes. For the rest who just want to use the framework:

The script path of the demo page is valid only if you extract the source files to the wwwroot path. If you extract them to another directory you should change the CallBackScriptPath of the application settings of web.config accordingly.

Follow the four steps that I have mentioned in the Using the code section of this article.

Read the comments of AjaxPage.cs, CallBackHelper.cs and ICallBackEventHandler.cs.

Read the comments of the public properties of AjaxPanel.

Read the comments of the AjaxLinkButton control.

You can send custom JavaScript to the client by using CallBackHelper.Write.

You must use the CallBackHelper.Request and CallBackHelper.Response properties in the code of your page, unless it inherits from AjaxPage. The same applies to your user controls and the AjaxUserControl that are provided.

Do not use Panel controls inside an AjaxPanel because even if only one child of the Panel changes, all its children will have to be rendered on the client. Use AjaxPanel controls instead.

After the update of 23-9-2005, the framework can handle the Response.Redirect and Server.Transfer methods during a callback. You don't have to change them in your code.

Use CallBackHelper.End instead of Response.End during a callback.

Always keep in mind that the callback is different from postback, because the page and its controls are persisted in the session, thus the Load event is not raised, and you can add/remove controls from the AjaxPanel dynamically, and the changes will be reflected on the browser. A bit like manipulating the controls of a desktop application.

Points of interest

AjaxPanel handles the "Browser Back Button" problem as well. The "Browser Back Button" problem is that by pressing the Back button, the browser loads the HTML page by its cache, so any AJAX changes made to the page are lost, while the user still expects to see the same page that he was viewing before.

To solve this, I have put in the page a JavaScript function that executes every time the page is loaded and a hidden field that is empty. The function checks this hidden field, and if it's empty, it assumes that the page was loaded by a request to the server (i.e. by the Refresh button) and sets the value of the field. If the function finds that the field is not empty, it assumes that the browser loaded the page by the Back button (the value of the fields are then restored) and invokes a special callback (CallBackStartup) on the server. When the CallBackStartup event is raised, AjaxPanel renders all of its children on the client page, thus restoring the previous page that the user was viewing.

Conclusion

I hope you find this framework useful. I encourage you to experiment with it, and if some fancy, jaws-dropping, eyes-bleeding, amazing control comes out of it, please share it with us.

About the Author

Comments and Discussions

any idea how many simultaneous requests can be fired async from the client browser to the server ?
i have played with it and was only able to get 2 requests handled at the same time at the server, although in the client code i fire like, 5 requests