Introduction
When developing an ASP.NET web site that uses an AJAX functionality, sooner or later, you realise that the process of developing AJAX based web controls starts getting repetitive: you end up writing the same JavaScript for creating and handling the XmlHttpRequest
s on the client side as well as on the server side. At this point, you probably already have an external JavaScript file that is referenced in your master page, and then you realise it all begins to evolve into some blown up JavaScript monster. Well, at least, that was what I experienced. When I finally had to face the problem of one control that needed to be instantiated several times on a page, I stopped and thought about a more elegant solution to my problems. What if I had an abstract AJAXControl
class that I could derive my controls from, only adding the JavaScript (and server-side handling) explicitly used by this control?
Background
What you need to know to fully understand this article is how AJAX works. I won't go into the details of that, what I'm going to show you here is how to create a base class that allows for AJAX development with a minimum of effort. You're going to see how a single control can both render its contents and handle client-side JavaScript requests, how to embed JavaScript into a custom control DLL, and how to register it.
Using the code
Just unzip the source code. There's the main project, AbstractAjaxControl, which contains the abstract control as well as a demo control that is derived from our abstract base class (and thus can be instantiated), and a simple web site using the said demo control.
Catching AJAX requests
I'm only about to cover AJAX requests that are posted with HTTP_POST. An AJAX POST request is triggered by two lines of JavaScript, before actually sending the data:
httpRequest.open('POST', uri, true);
where 'POST' specifies we're going to use HTTP_POST, uri
is the address of the document we're posting to, and the last argument sets the request to blocking (false
, synchronous) or non-blocking (true
, asynchronous).
httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
This tells the JavaScript to send the request as if a form was posted. There is no difference of a form being sent by clicking the Submit button, or by sending an XmlHttpRequest
this way, except one, and this is what we use: ASP.NET does recognize the Request.Form
property, but it does not recognize a postback event. This is a pretty rare condition on ASP.NET-driven web sites, and that's why we'll use it.
We want our abstract base class to handle the said condition; we want the derived classes just to implement how an AJAX request is answered. For this purpose, I declared a private
event AjaxRequest
:
private delegate void AjaxRequestEventHandler(object sender, AjaxRequestEventArgs e);
private event AjaxRequestEventHandler AjaxRequest;
private void OnAjaxRequest()
{
if (AjaxRequest != null)
AjaxRequest(this, new AjaxRequestEventArgs(Page.Request.Form));
}
AjaxRequestEventArgs
is an EventArgs
class that stores all form elements in the Request.Form NameValueCollection
in its public property Parameters
.
Question is: when should the event be triggered?
- there's no postback event
- there's content in our
Request.Form NameValueCollection
- this content is of interest, e.g., in the scope of the control
- before the rest of the page gets initialised (or rendered)
See point c), we need a variable to set the scope, and we need all our JavaScript's that send data to deliver the scope of this data. I did this server-side, by declaring a private
member _scope
with a publicly available property Scope
(that can and should be set declaratively by derived classes). Scope
will come in handy if there's more than one control derived from our abstract class in one page.
Finding the right moment to trigger the AjaxRequest
is easy because of condition d). We need to override the OnInit
method.
if (!Page.IsPostBack && Page.Request.Form["scope"] == _scope)
OnAjaxRequest();
base.OnInit(e);
That's it, as far as the server-side handling of the AJAX request is concerned. Don't forget to subscribe to the event, though, and tell all derived classes they absolutely have to implement a method to handle the AJAX request:
public AjaxControl()
{
AjaxRequest += new AjaxRequestEventHandler(HandleAjaxRequest);
}
protected abstract void HandleAjaxRequest();
How much JavaScript do we need?
We're half-way done. Remember, we don't want any blown-up JavaScript file that needs to be referenced in the master page? We want a base JavaScript that gets used by all controls derived from our abstract base class, plus little snippets of JavaScript that define the behaviour of derived controls and are deployed only with these. Think about it: what have all AJAX controls you can think of in common? What JavaScript functions belong to the base functionality?
- a declaration of a (global)
XmlHttpRequest
object - a function to create such an object
- a function to prepare given data and send them
What's different about all AJAX JavaScript's is the handling of the response, so that is something we cannot put in the base JavaScript file. What's left is this:
var httpRequest = null;
function CreateRequest()
{
if (window.XMLHttpRequest)
httpRequest = new XMLHttpRequest();
else if (window.ActiveXObject)
httpRequest = new ActiveXObject("Msxml2.XMLHTTP");
}
function SendRequest(parameters, responseHandler)
{
if(httpRequest == null)
CreateRequest();
httpRequest.open('POST', document.location.href, true);
httpRequest.setRequestHeader('Content-Type',
'application/x-www-form-urlencoded');
httpRequest.onreadystatechange = responseHandler;
strPostData = "";
for(i = 0; i < parameters.length; i++)
{
strPostData += parameters[i] + "=";
i++;
strPostData += parameters[i];
if(i + 1 != parameters.length)
strPostData += "&";
}
httpRequest.send(strPostData);
}
My global XmlHttpRequest
object is httpRequest
, which is created once (when it's needed the first time). In the CreateRequest
method, I first check if XmlHttpRequest
is known (true for IE 7, Opera, Mozilla) and fall back to the aged ActiveXObject
if that fails. SendRequest
takes an array of parameters as key-value-pairs (that is: { "key1", "value1", "key2", "value2"... } and so on), prepares the parameters as a string to send, and sends it using HTTP_POST, asynchronously, to the page that it was called from (remember: our control handles its own AJAX request - that's why the URI that the request is sent to is the URI of the page that contains our control).
Embed your script
The part that I had the most headache with was how to embed the script in my dll and use it from there. As it turned out, it's not that hard if you know how to do it. It's four steps:
- create a resource file for your project (right-click the project, Properties->Resources)
- add a .js file to your resources
- set the Build property of your .js file to "embedded resource": right-click the js file (typically put in a subfolder "resources"), and there you are
- set the
assemby:webresource
attribute to:
[assembly: WebResource("ProjectName.Resources.Filename.js", "text/javascript")]
This attribute is inserted directly above the namespace declaration in the base class file.
Try avoiding any dots in your JavaScript file name. Believe me, this can save you some years life expectance.
Once you have embedded the script, you can register it with the ASP page your control resides in. Do this with the exact name you used in the attribute above; for example, I register the base AJAX JavaScript in my overridden OnInit
method, right after calling the base.OnInit
:
Page.ClientScript.RegisterClientScriptResource(typeof(AjaxControl),
"AbstractAjaxControl.Resources.AjaxBase.js");
You can register that at almost any point in your control code.
The demo control
Because you tend to get disappointed if you try to instantiate an abstract class (after all, that's why it is abstract), I enclosed a class that derives from AjaxControl
to show you how to do that. It's pretty straightforward:
- add a new class
- derive it from
AjaxControl
- implement the control as if you'd write a normal ASP.NET control (that is: take care of the
RenderContents()
method, or the CreateChildControls()
method if you're more like me) - implement the inherited
HandleAjaxRequest()
method (and make sure it calls Response.End()
, we don't want any more processing of the page to occur) - write a JavaScript method to handle an
XmlHttpResponse
, and one to prepare and call the SendRequest()
method - if you like, embed your JavaScript in your DLL. You don't have to, though.
That's it.
Points of interest
It's true, this sort of approach is much more effort than using the ASP.NET AJAX extension. On the other hand, it's way less overhead, and you have full control over what your page does and does not. And there are environments where no ASP.NET AJAX is installed, and installation is no option. That's what drove me to do the whole AJAX thing by hand in the first place.
If you can, try and use your JavaScript's as embedded resources. No longer maintaining a growing JavaScript folder really pays off.
History
- First version, no history yet: 07/07/27.