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)
public class 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:
protected override void OnCallBack(EventArgs e)
{
PutSessionIDInCache();
txtMsg.Text = chatData.msgText.ToString();
ShowNames();
base.OnCallBack (e);
}
If the page doesn't inherit from AjaxPage
, it can handle the callback event by implementing the ICallBackEventHandler
interface:
public interface 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
Put:
<httpModules>
<add name="AjaxHttpModule" type="Ajax.AjaxHttpModule, Ajax" />
</httpModules>
at the <system.web>
section, and:
<add key="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)
CallBackHelper.SetCallBackTimerInterval(3000);
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:
Button btnSend = new Button();
btnSend.Attributes.Add ("onclick",
CallBackHelper.GetCallbackEventReference(btnSend) +
" return false;");
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:
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.
History
- 16-9-2005 - Initial release.
- 16-9-2005 - Added in the article the browser detection limitation, noted by Cristian O.
- 18-9-2005
- Inheriting from
AjaxPage
is no longer necessary to use the AJAX Framework. Moved its functionality to other classes except the handling of the callback event and the Request
/Response
properties. - Added AjaxUserControl.cs with similar functionality as
AjaxPage
. - Replaced the
AjaxHttpHandler
with the AjaxHttpModule
. - Added IPostDataLoadedEventHandler.cs.
- Added NoVerifyRenderingPage.cs.
- Added a few helper functions and properties to CallBackHelper.cs.
- Other minor additions and improvements.
- Updated the text of the article.
- Fixed a bug in
SetCallBackForChildPostBack
of AjaxPanel
, noted by Cristian O.
- 21-9-2005
- Fixed CallBackObject.js to make checkboxes work properly (noted by collab man).
- Added the
CallBackHelper.Redirect
method and included it in the Sum up section of the article (noted by mdissel).
- 23-9-2005
AjaxHttpModule
can now handle Response.Redirect
and Server.Transfer
during a callback. CallBackHelper.Redirect
is not required. Updated the article in the Sum up section.
- 27-9-2005
- Fixed a bug noted by collab man (controls not getting refreshed when callback is invoked by
CheckBox
or ListBox
). - Fixed a bug noted by JunkyMail1 (multi-select
ListBox
not working properly). - Enhancements to CallBackObject.js provided by Cristian O. ("Loading..." indicator, callback timeout, response returned during an error is displayed on page).
- 30-9-2005
AjaxPanel
works properly now when its Visible
property is changed. - Fixed the hidden controls persisting their state bug noted by mdissel.
- 6-10-2005
- Fixed the
RadioButtonList
problem noted by CarinLindberg.
- 8-10-2005
- In CallBackObject.js, fixed the post data not getting sent correctly when they contain '+', noted by Ricardo Stuven.
- 11-10-2005
- Added the fix for handling
CheckBoxList
, provided by Ricardo Stuven.
- 12-11-2005
- Included in the article the SourceForge and homepage link for MagicAjax.