Click here to Skip to main content
Click here to Skip to main content
Go to top

Creating an abstract AJAX user control

, 26 Jul 2007
Rate this:
Please Sign up or sign in to vote.
This article describes the steps needed to create an abstract class that already takes care of AJAX requests of derived controls.

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 XmlHttpRequests 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?

  1. there's no postback event
  2. there's content in our Request.Form NameValueCollection
  3. this content is of interest, e.g., in the scope of the control
  4. 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?

  1. a declaration of a (global) XmlHttpRequest object
  2. a function to create such an object
  3. 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:

  1. create a resource file for your project (right-click the project, Properties->Resources)
  2. add a .js file to your resources
  3. 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
  4. set the assemby:webresource attribute to:
  5. [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:

  1. add a new class
  2. derive it from AjaxControl
  3. 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)
  4. implement the inherited HandleAjaxRequest() method (and make sure it calls Response.End(), we don't want any more processing of the page to occur)
  5. write a JavaScript method to handle an XmlHttpResponse, and one to prepare and call the SendRequest() method
  6. 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

  1. First version, no history yet: 07/07/27.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

IlTrips
Web Developer
Germany Germany
No Biography provided

Comments and Discussions

 
GeneralError in mscorlib.dll Pinmembermajdbaddour29-Jul-07 17:18 
GeneralRe: Error in mscorlib.dll PinmemberIlTrips29-Jul-07 23:34 
I don't experience that exception here, however, the problem sounds familiar. I remember that it occurred in a project where I called Response.Redirect(). Read http://support.microsoft.com/kb/312629 for a description of why it happens, and how to prevent it. Can't quite catch the reason why it happens here, though. It could very well be the simple call of Response.End() should be altered. In fact, the abstract control I use in my AJAX control library uses another approach to stop sending data once the (XML) response has been sent. I suggest the following: let HandleAjaxRequest call a protected virtual method that handles the construction and sending of the XML response, then handle the ending of the response there. In derived classes, override the called virtual method, not HandleAjaxRequest itself. That way, you can catch the exception, AND you don't have to watch out to end the response in derived classes (which is a bad idea anyway, because you can't expect people who write new controls to know they need to end the response...).
 
Hope that helps.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140916.1 | Last Updated 27 Jul 2007
Article Copyright 2007 by IlTrips
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid