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 default DIV
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;
}
target_div = (target == null) ? dataholder : target;
target_div.innerHTML =
"<B><I>Getting data. Please wait ..."</I>"<B>";
if (window.XMLHttpRequest)
ajaxobj = new XMLHttpRequest();
if (window.ActiveXObject)
ajaxObj = new ActiveXObject("microsoft.xmlhttp");
ajaxObj.onreadystatechange = ProcessResponse;
ajaxObj.open("GET", "Data.acall?" + querystring);
ajaxObj.send(null);
}
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 the Request
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 for Authenticated
or Authorized
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>
<httpModules>
<ADD type="DataBrowser.AjaxCallModule,DataBrowser"
name="ajaxcalls" />
</httpModules>
<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()
{
_response.Clear();
try
{
PrepareConnectionString();
typeof(AjaxCallProcessor).GetMethod(_request["method"],
BindingFlags.NonPublic|BindingFlags.Instance).Invoke(this, null);
HtmlTextWriter hw = new HtmlTextWriter(_response.Output);
dynamicControl.RenderControl(hw);
hw.Close();
if (sqlConn.State == ConnectionState.Open)
sqlConn.Close();
}
catch (Exception ex)
{
_response.Write("<STRONG><FONT color=red>Error : ");
_response.Write(ex.Message);
_response.Write("</FONT></STRONG>");
}
_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's responseText
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 provided DIV
tag using the innerText
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()
{
if (ajaxObj.readyState == 4)
{
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:
References