Implementing an extensible factory method pattern using ASP.NET and AJAX






4.50/5 (6 votes)
Nov 3, 2005
6 min read

74376

235
This article discusses the advantages of building HTML controls on the server side for AJAX calls and proposes an extensible factory pattern. Also different approaches for handling these custom calls using modules, handlers and ASPX pages and the leverage provided by ASP.NET libraries are discussed.
Introduction
The general approach taken for dynamically building pages using AJAX (Asynchronous JavaScript and XML) is to use client side scripting to call a web service (or a page) to retrieve data and populate a control which is already available on the client side. This approach has some advantages and disadvantages.
The advantage is that the GUI elements are already placed and the page layout is well known. The disadvantages are:
- XML is transferred by the web service or page which requires strict XML contract (defined schema) between the client and the server.
- Client side coding is required to load and parse XML using the bDOM document.
- The code for adding data to HTML controls is different for each HTML element. For e.g. the drop down list (
SELECT
,OPTION
) is different from a table (TABLE
,TR
,TD
).
These problems can be addressed if the controls are created on the server side, populated with data and the HTML along with the data is serialized to the client. The only burden on the client side would be to have a place holder available for the HTML element returned.
ASP.NET web class libraries offer excellent support to create HTML elements as classes which support rendering methods that provide an excellent opportunity to address this problem.
To implement a factory method pattern I have separated every call into two sections:
- The method that has to be invoked on the server is sent in the query string as a key named 'method'. This method name is retrieved by a processor class on the server side and dynamically invoked using Reflection (you can have a class name too).
- The actual parameters required by the method (to keep the design simple) are sent as key value pairs in the query string. This way the method on the server side can retrieve the required parameters from the
Request
object.
To accommodate and render the HTML element returned by the processor on the client side a DIV
tag is created as the target element. As soon as the asynchronous call returns the script will take the responseText
and using the innerHTML
property will add this code inside the DIV
tag which will render the control on the page. If there are client side scripts associated with this dynamically created element, optionally the ID for the created element may be set on the server side (which requires it to be passed as another parameter in the query string like the setID
in the demo program).
The code for the solution can be separated into the following:
- The initial AJAX calling sequence.
- Handling the code in ASP.NET (the server side code).
- Processing the response on the browser side.
The initial client call sequence
- All the required parameters are added to a query string (this includes a connection string in my example). This is method specific. For e.g. see the
GetTables()
method. It requires only a connection string. So the client side JavaScript adds them to a query string and the server side method retrieves them. - Query string is appended to the URL. The URL and the extension are dependent on the server side code implementation by the module, handler or a simple ASPX page.
- A target
DIV
tag is provided by the caller to display the returned results. A defaultDIV
is assumed if nothing is provided. I have added a 'results'DIV
in the demo for this purpose. - An asynchronous call is made using the
XMLHTTP
object with a call back function. Take care to verify this on all browsers. The following code in DataBrowser.js file makes this call:function SendRequest(target) { AppendConnectionParameters(); if (ajaxObj != null) { target_div.innerHTML = ""<B>Already in call. Please wait ..."</B>"; return; // object is busy } target_div = (target == null) ? dataholder : target; target_div.innerHTML = "<B><I>Getting data. Please wait ..."</I>"<B>"; // for Mozilla, Firefox, Safari, and Netscape if (window.XMLHttpRequest) ajaxobj = new XMLHttpRequest(); // for Internet Explorer if (window.ActiveXObject) ajaxObj = new ActiveXObject("microsoft.xmlhttp"); ajaxObj.onreadystatechange = ProcessResponse; // set the call back // You can make the call in any of the following 3 ways. // (1) opening a aspx call, the default asp.net page // handler will be called to process aspx page // ajaxObj.open("GET", "AjaxCallPage.aspx?" + querystring); // (2) use a custom ASP.NET extension handler // ajaxObj.open("GET", "Data.acall?" + querystring); // (3) use a custome extension with a ASP.NET module installed, // no change in call from above // ajaxObj.open("GET", "Data.acall?" + querystring); ajaxObj.open("GET", "Data.acall?" + querystring); ajaxObj.send(null); // send the request }
Handling the code inside ASP.NET (the server side code)
The module, filter, page model provided by ASP.NET is very flexible and gives excellent opportunity to extend the ASP.NET functionality.
A custom call from a client can be handled in three ways:
- By using an ASPX page- This is the simplest and involves least development effort. In the example code provided the call is handled in the
Init()
method and the response stream is closed. This avoids the overhead of other events like loading, rendering and others at the page level. - Providing a custom handler- This is bypassing the default ASP.NET page handler. Once all the modules configured in machine.config or web.config finish their processing the call is handed over to our handler. This approach gives the advantage of a custom extension and bypasses any overhead the default page handler has. This involves adding the extension in IIS and adding the handler in the CONFIG file (see below).
- Writing a custom module- A custom module can handle the call very early in the sequence of events inside ASP.NET and also provides the advantage of bypassing some modules and a handler. There are two scenarios in writing a custom module. The
BeginRequest
is the earliest event where theRequest
object is available to the module. If you do not want any authentication or authorization this is the quickest way to return the data back to the client. But if you are targeting security subscribe forAuthenticated
orAuthorized
event and handle the request. This approach like the handler requires registration of the extension with IIS and also adds the module to the CONFIG file (as shown below):<configuration> <system.web> <!-- registering a module --> <httpModules> <ADD type="DataBrowser.AjaxCallModule,DataBrowser" name="ajaxcalls" /> </httpModules> <!-- registering a handler --> <httpHandlers> <ADD type="DataBrowser.AjaxCallHandler,DataBrowser" path="*.acall" verb="*"/> </httpHandlers> ....... <system.web> <configuration>
To make all the above scenarios possible, the AjaxCallProcessor
class is provided which handles all the logic. This class invokes a method dynamically using Reflection which returns the GUI element which is dynamically created and populated with data (depending on the client parameters read from the Request
object).
The code below shows the ProcessAjaxCall()
method on AjaxCallProcessor
class. This can also be made a static
method. I made it an instance method to simplify the code:
internal void ProcessAjaxCall()
{
// clear any headers added to the response
// header by ASP.NET HTTP pipe
_response.Clear();
try
{
// prepare connection string, part of business
// logic in this demo
PrepareConnectionString();
// invoke the method requested by the client
// dynamically using reflection
typeof(AjaxCallProcessor).GetMethod(_request["method"],
BindingFlags.NonPublic|BindingFlags.Instance).Invoke(this, null);
// create a HTMLTextWriter for the response stream
// and let the dynamic control to create it
HtmlTextWriter hw = new HtmlTextWriter(_response.Output);
// let the asp.net control render itself
dynamicControl.RenderControl(hw);
hw.Close();
if (sqlConn.State == ConnectionState.Open) // just in case :-)
sqlConn.Close();
}
catch (Exception ex)
{
// writing back an error message as HTML to response stream
_response.Write("<STRONG><FONT color=red>Error : ");
_response.Write(ex.Message);
_response.Write("</FONT></STRONG>");
}
// close the response stream to end the call immediately
// this will bypass any delay in ASP.NET HTTP pipe
_response.End();
}
Processing the response on the browser side
- Inside the call back function, check the response of the asynchronous call.
- If
readyState
is 4 the call is completed and the object'sresponseText
gives the reply from the server. - The response is expected to be complete HTML (as it is designed). Take the
responseText
and add it inside the providedDIV
tag using theinnerText
property.Note: Sometimes you may not want to lose the actual control in place in case of an exception on the server side. You can check for a pre-fix or something in the
responseText
and handle it accordingly. For e.g. instead of the HTML pass the error message in the<CUSTOM_ERRROR>
tag and check for this tag on the client side.function ProcessResponse() { // target_div.innerHTML += ajaxObj.readyState; // for testing only if (ajaxObj.readyState == 4) // request completed { target_div.innerHTML = ajaxObj.responseText; ajaxObj = null; target_div = null; } }
The demo program included demonstrates the use of this technique. Please see the screen shots below which show you how a control is replaced on the client side: