Background
Asynchronous JavaScript and XML (AJAX) has recently become the craze thanks, in no small part, to Google’s usage of it in Google Suggest as well as Google Maps. In ASP.Net terms,Enter Michael Schwarz's
It should be pointed out that technologies such as
|
To learn more about |
How it works - Overview
The wrapper itself works by marking .Net functions as
Complicated? It isn't. Let's look at a simplified example. Given the .Net function:
public function Add(firstNumber as integer, secondNumber as integer) as integer
return firstNumber + secondNumber
end sub
public int Add(int firstNumber, int secondNumber)
{
return firstNumber + secondNumber;
}
The
Initial Setup
We'll first go through the steps of "installing" the .dll for use in your project. If you know how to add a reference to a .dll file, skip this section.
First, if you don’t already have it, download the latest version of AJAX. Unzip the downloaded file and place the ajax.dll within a ref folder of your project. In Visual Studio.Net, right click the "References" node in the Solution Explorer and select Add Reference. In the newly opened dialog, click Browse and navigate to the ref/ajax.dll file. Click Open followed by Ok. You are now ready to start programming with the
|
If you’re having difficulty setting up the reference, check out: |
The first step necessary to make everything work is to set up the wrapper's HttpHandler in the web.config. Without going into detailed explanation of what and how HttpHandlers work, it's sufficient to know that that they are used to process ASP.Net requests. For example, all requests for *.aspx pages are handled by the System.Web.UI.PageHandlerFactory class. Similarly we'll make all requests to
<system.web>
<httpHandlers>
<add verb="POST,GET" path="ajax/*.ashx"
type="Ajax.PageHandlerFactory, Ajax" />
</httpHandlers>
<system.web>
</configuration>
The above code basically tells ASP.Net that any requests made that match the specified path (
Setting up the Page
We are now ready to start coding. Create a new page, or open an existing one and in the code behind file, add the following code in the Page_Load event:
Public Class Index
Inherits System.Web.UI.Page
Private Sub Page_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Ajax.Utility.RegisterTypeForAjax(GetType(Index))
'
end sub
'
End Class
public class Index : System.Web.UI.Page{
private void Page_Load(object sender, EventArgs e){
Ajax.Utility.RegisterTypeForAjax(typeof(Index));
//
}
//
}
<script language="javascript"
src="ajax/NAMESPACE.PAGECLASS,ASSEMBLYNAME.ashx"></script>
Where the bolded parts have the following meaning:
NAMESPACE.PAGECLASS |
The namespace and class of the current page (this will typically be the value of the Inherits attribute in the @Page directive) |
ASSEMBLYNAME |
The name of the assembly the current page is part of (this will typically be the name of your project) |
Bellow is a sample output for the sample.aspx page in an AjaxPlay project:
<html>
<head>
<script language="javascript" src="ajax/common.ashx"></script>
<script language="javascript"
src="ajax/AjaxPlay.Sample,AjaxPlay.ashx"></script>
</head>
<body>
<form id="Form1" method="post" runat="server">
</form>
</body>
</html>
Even if you don’t know how HttpHandlers work, the above should be understandable. Via the web.config, we’ve made sure that all requests that go to
Creating the Server-Side Functions
We’ll now create a server-side function that’ll be asynchronously be available from a client-side call. Since not all return types are currently supported (don’t worry, upcoming versions will build on what’s currently there), we’ll stick with our simple ServerSideAdd functionality. In the code behind file, add the following method to your page class:
<Ajax.AjaxMethod()> _
Public Function ServerSideAdd (byval firstNumber As Integer, byval secondNumber
As Integer) As Integer
Return firstNumber + secondNumber
End Function
[Ajax.AjaxMethod()]
public int ServerSideAdd(int firstNumber, int secondNumber)
{
return firstNumber + secondNumber;
}
Making our client-side call
The last step is to call the function using JavaScript. The
<html>
<head>
<script language="javascript" src="ajax/common.ashx"></script>
<script language="javascript"
src="ajax/AjaxPlay.Sample,AjaxPlay.ashx"></script>
</head>
<body>
<form id="Form1" method="post" runat="server">
<script language="javascript">
var response = Sample.ServerSideAdd(100,99);
alert(response.value);
</script>
</form>
</body>
</html>
function ServerSideAdd_CallBack(response){
if (response.error != null){
alert(response.error);
return;
}
alert(response.value);
}
value |
The actual return value (be it a string, custom object or dataset) of the server-side function. |
error |
An error message, if any. |
request |
The raw response from the xml http request. |
context |
A context object. |
First we check the error value to see if any errors occurred. You can easily play with the error property by throwing an exception in the server-side function. Then, in this simplified case, we alert the value. The request property can be used to get additional information (see box bellow).
|
To learn more about the Xml Http request, check out the following link dump: http://www.quirksmode.org/blog/archives/2005/02/xmlhttp_linkdum.html |
Dealing with Types
Returning complex types
The
Returned DataSets work much like real .Net DataSet. Given a server side function which returns a DataSet, we could display the contents client side via:
//Asynchronous call to the mythical "GetDataSet" server-side function
function getDataSet(){
AjaxFunctions.GetDataSet(GetDataSet_callback);
}
function GetDataSet_callback(response){
var ds = response.value;
if(ds != null && typeof(ds) == "object" && ds.Tables != null){
var s = new Array();
s[s.length] = "<table border=1>";
for(var i=0; i<ds.Tables[0].Rows.length; i++){
s[s.length] = "<tr>";
s[s.length] = "<td>" + ds.Tables[0].Rows[i].FirstName + "</td>";
s[s.length] = "<td>" + ds.Tables[0].Rows[i].Birthday + "</td>";
s[s.length] = "</tr>";
}
s[s.length] = "</table>";
tableDisplay.innerHTML = s.join("");
}
else{
alert("Error. [3001] " + response.request.responseText);
}
}
</script>
public class User{
private int _userId;
private string _firstName;
private string _lastName;
public int userId{
get { return _userId; }
}
public string FirstName{
get { return _firstName; }
}
public string LastName{
get { return _lastName; }
}
public User(int _userId, string _firstName, string _lastName){
this._userId = _userId;
this._firstName = _firstName;
this._lastName = _lastName;
}
public User(){}
[AjaxMethod()]
public static User GetUser(int userId){
//Replace this with a DB hit or something :)
return new User(userId,"Michael", "Schwarz");
}
}
Utility.RegisterTypeForAjax(typeof(User));
Allowing us to asynchronously call the GetUser in client-side code with code such as:
function getUser(userId){
User.GetUser(GetUser_callback);
}
function GetUser_callback(response){
if (response != null && response.value != null){
var user = response.value;
if (typeof(user) == "object"){
alert(user.FirstName + " " + user.LastName);
}
}
}
getUser(1);
</script>
Custom Converters
As we’ve seen, the
This guide will be updated with additional information on custom converters shortly (sorry).
Miscellaneous
Registering functions in another class
In the above example, our server-side functions resided within the code behind of the executing page. However, there’s no reason why these functions can’t be in a separate class file. Remember, the way the wrapper works is to find all methods within the specified class that have the
<Ajax.AjaxMethod()> _
Public Function Validate(username As String, password As String) As Boolean
'do something
'Return something
End Function
End Class
We could have the
Private Sub Page_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Ajax.Utility.RegisterTypeForAjax(GetType(AjaxFunctions))
'
End Sub
private void Page_Load(object sender, EventArgs e){
Ajax.Utility.RegisterTypeForAjax(typeof(AjaxFunctions));
//
}
Remember, the client-side proxy takes the name of <ClassName>.<ServerSideFunctionName>. Therefore, if our ServerSideAdd function was located in the fictional AjaxFunctions class above, our client-side call would be: AjaxFunctions.ServerSideAdd(1,2)
How the proxy really works
The second script tag generated by the
Returning Unicode characters
public string Test1(string name, string email, string comment){
string html = "";
html += "Hello " + name + "<br>";
html += "Thank you for your comment <b>";
html += System.Web.HttpUtility.HtmlEncode(comment);
html += "</b>.";
return html;
}
It’s likely that you’ll need to access session information in your server side function. To do so, you must simply tell
While looking at the session capabilities of the wrapper, let’s look at a couple other features. In this example, we have a document management system which puts a lock on a document while a user is editing it. Other users can request to be notified when the document because available. Without
First we’ll write our server side function, the goal of which is to loop through the documentIds the user wishes to edit (stored in a session) and return all released documents.
<Ajax.AjaxMethod(HttpSessionStateRequirement.Read)> _
Public Function DocumentReleased() As ArrayList
If HttpContext.Current.Session("DocumentsWaiting") Is Nothing Then
Return Nothing
End If
Dim readyDocuments As New ArrayList
Dim documents() As Integer = CType(HttpContext.Current.Session("DocumentsWaiting"), Integer())
For i As Integer = 0 To documents.Length - 1
Dim document As Document = document.GetDocumentById(documents(i))
If Not document Is Nothing AndAlso document.Status = DocumentStatus.Ready Then
readyDocuments.Add(document)
End If
Next
Return readyDocuments
End Function
[Ajax.AjaxMethod(HttpSessionStateRequirement.Read)]
public ArrayList DocumentReleased(){
if (HttpContext.Current.Session["DocumentsWaiting"] == null){
return null;
}
ArrayList readyDocuments = new ArrayList();
int[] documents = (int[])HttpContext.Current.Session["DocumentsWaiting"];
for (int i = 0; i < documents.Length; ++i){
Document document = Document.GetDocumentById(documents[i]);
if (document != null && document.Status == DocumentStatus.Ready){
readyDocuments.Add(document);
}
}
return readyDocuments;
}
}
Notice that we specify the HttpSessionStateRequirement.Read value (alternatives being Write and ReadWrite).
Now we write our JavaScript to take advantage of this method:
function DocumentsReady_CallBack(response){
if (response.error != null){
alert(response.error);
return;
}
if (response.value != null && response.value.length > 0){
var div = document.getElementById("status");
div.innerHTML = "The following documents are ready!<br />";
for (var i = 0; i < response.value.length; ++i){
div.innerHTML += "<a href=\"edit.aspx?documentId=" + response.value[i].DocumentId + "\">" + response.value[i].Name + "</a><br />";
}
}
setTimeout('page.DocumentReleased(DocumentsReady_CallBack)', 10000);
}
</script>
<body onload="setTimeout('Document.DocumentReleased(DocumentsReady_CallBack)', 10000);">
Our server side function is called once on page load and subsequently every 10 seconds. The call back function checks the response to see if any values were returned, and if so displays the newly available documents to the user in a div tag.
Conclusion
|
Keep a close eye on the |
|
For a good hands-on sample, check out the following demo application: |