Background. 1
How it works - Overview. 2
Initial Setup. 2
Setting up the HttpHandler2
Setting up the Page. 3
Creating the Server-Side Functions4
Making our client-side call5
Dealing with Types6
Returning complex types6
Custom Converters8
Miscellaneous8
Registering functions in another class8
How the proxy really works8
Returning Unicode characters9
SessionState. 9
Conclusion. 10
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, AJAX allows server-side processing to occur without requiring postback, thus enabling clients (browsers) with rich server-side capabilities. In other words it provides a framework for asynchronously dispatching and processing requests and responses from the server. AJAX leverages a number of existing technologies, which aren't particularly new, however fondness for what these technologies have to offer (collectively, they are AJAX) has recently soared.
Enter
Michael Schwarz
's AJAX .Net wrapper which allows ASP.Net developers to quickly and easily deploy pages capable of easily capitalizing on AJAX. Users should be cautioned that the wrapper is early in development, and as such isn't fully matured.
It should be pointed out that technologies such as AJAX are very likely going to lead to violations of layered architectures (N-Tier). My opinion is that AJAX increases the likelihood that the presentation logic layer (or worse, the business layer) will leak into the presentation layer. Strict architects, like me, might cringe at this notion. I feel that even if AJAX is used in a manor which slightly violates layer boundaries, the payoffs are well worth it. Of course, that's something you'll need to look at for your specific project and environment.
AJAX
relies on a broker to dispatch and process requests to and from the server. For this task, the .Net wrapper relies on the client-side
XmlHttpRequest
object. The
XmlHttpRequest
object is well supported by most browsers, making it the solution of choice. Since the goal of the wrapper is to hide the implementation of
XmlHttpRequest
, we'll forgo any detailed discussion about it.
The wrapper itself works by marking .Net functions as AJAX methods. Once marked, AJAX creates corresponding JavaScript functions which can be called client-side (liky any JavaScript functions) and that serve as proxies, using
XmlHttpRequest
. These proxies map back to the server-side function.
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 AJAX .Net wrapper will automatically create a JavaScript function called "Add" which takes two parameters. When this function is called using JavaScript (on the client), the request will be passed to the server and the return value back to the client.
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 AJAX .Net wrapper.
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 ajax/*.ashx handled by the
Ajax.PageHandlerFactory
:
<configuration>
<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 (ajax/*.ashx) are to be handled by
Ajax.PageHandlerFactory
, instead of the default handler factory. You don’t need to create an ajaxsubdirectory, this is a mythical directory simply used so that other
HttpHandlers
can use the .ashx extension with their own made-up subdirectories.
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:
Private Sub
Page_Load(sender
As
Object, e
As
EventArgs)
Handles MyBase
.Load
Ajax.Utility.RegisterTypeForAjax(
GetType
(Index))
end sub
End Class
|
private void
Page_Load(
object
sender, EventArgs e){
Ajax.Utility.RegisterTypeForAjax(
typeof(
Index
)
);
}
}
|
The call to
RegisterTypeForAjax
emits the following JavaScript on the page (alternatively, you could manually place the following two lines on the page):
<script
language
="
javascript
"
src
="
ajax/common.ashx
"></script>
<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:
<%@ Page
Inherits
="
AjaxPlay.Sample
"
Codebehind
="
sample.aspx.cs
"
%>
<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>
|
You can test that everything is working properly by manually navigating to the src paths in your browser (view source and copy and paste the paths). If both paths output some (seemingly) meaningless text, you’ve done well so far. If they’re outputting nothing or ASP.Net errors, something was improperly done.
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 ajax/*.ashx are processed by the custom handler. Obviously, the two script tags will be processed by this custom handler.
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;
}
|
Note that the functions have the
Ajax.AjaxMethod
attribute set. The attribute serves to tell that wrapper to create JavaScript proxies for these methods so that they might be called client-side.
The last step is to call the function using JavaScript. The AJAX wrapper took care of creating a JavaScript function called
Sample.ServerSideAdd
function which takes two parameters. For the most basic functionality, all we need to do is call the method and pass two numbers:
<%@ Page
Inherits
="
AjaxPlay.Sample
"
Codebehind
="
sample.aspx.cs
"
%>
<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>
|
Of course, we’ll want to use this powerful capability for more than simply alerting the user. That’s why all client-side proxies (such as the JavaScript
Sample.ServerSideAdd
function), also accept an additional property. The property is the callback function called in order to process the response:
Sample.ServerSideAdd(100,99, ServerSideAdd_CallBack);
function
ServerSideAdd_CallBack(response){
if
(response.error != null){
alert
(response.error);
return
;
}
alert
(response.value);
}
|
We can see from the above code that we’ve specified an extra parameter.
ServerSideAdd_CallBack
(also shown above) is the client-side function used to process the response from the server. The callback function receives a response object which exposes three key properties:
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).
The Ajax wrapper is capable of handling more than just the integer our
ServerSideAdd
function returned. It currently supports
integers
,
strings
,
double
,
booleans
,
DateTime
,
DataSets
and
DataTables
, as well as the primitive types of custom classes and arrays. All other types have their
ToString
values returned.
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:
<script
language
="
JavaScript
">
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>
|
Ajax
can also return custom classes. All that is required is that the class be marked with the
Serializable
attribute. Given the following class:
[
Serializable()]
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){
return
new
User(userId,"
Michael
", "
Schwarz
");
}
}
|
We would register the
GetUser
proxy via a call to the
RegisterTypeForAjax
:
private
void
Page_Load(
object
sender, EventArgs e){
Utility.RegisterTypeForAjax(
typeof
(User));
}
|
Allowing us to asynchronously call the GetUser in client-side code with code such as:
<script
language
="
javascript
">
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>
|
The value returned in the response is actually an object which exposes the same properties as a server-side object (
FirstName
,
LastName
and
UserId
).
As we’ve seen, the Ajax .Net wrapper is able to deal with many different .Net types. However, aside from a handful of .Net classes and the built-in types, the wrapper simply calls
ToString()
on anything it can’t properly return. To get around this, the Ajax .Net wrapper allows developers to create object converters which can be used to smoothly communicate complex objects between the server and the client.
This guide will be updated with additional information on custom converters shortly (sorry).
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
. The class in question is specified via the second script tag. Using
Ajax.Utility.RegisterTypeForAjax
we can specify any class we want. For example, it would be reasonable to keep our server-side functions in a separate class:
Public
Class
AjaxFunctions
<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 Ajax wrapper create proxies for this class by specifying this class’s type instead of the pages:
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)
The second script tag generated by the Ajax utility (or manually inserted by you) passes the namespace, class name and assembly of the page. Armed with this information, the
Ajax.PageHandlerFactory
is able to use reflection and get the details about any functions which have a certain attribute. Obviously, the handler looks for functions with the
AjaxMethod
attribute, gets their signature (return type, name, and parameters) and is thus able to create the necessary client-side proxy. Specifically, the wrapper creates a JavaScript object named the same name as your class which exposes the proxy. In other words, given a server-side class
AjaxFunctions
with an Ajax method
ServerSideAdd
, we should expect a JavaScript object named
AjaxFunction
which exposes a
ServerSideAdd
function. You can see this in action by pointing your browser to the path of the second script tag.
Ajax
.Net wrapper is able to return Unicode characters from the server to the client. To do so, values must be html encoded on the server before being returned. For example:
[Ajax.AjaxMethod]
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 Ajax to enable such functionality via a parameter passed to the
Ajax.AjaxMethod
attribute.
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 AJAX, we’d need to wait until the user posted back in order to check if his or her queued documents were available. This is obviously not ideal. Using Ajax with session state support, this is quite easy.
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:
<script
language
="
javascript
">
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.
AJAX
technology has already lead to sleek and rich web interfaces, previously reserved for desktop development. The Ajax .Net wrapper allows you to easily take advantage of this new power. Note that both the Ajax .Net wrapper and the documentation are under development.
凡是有该标志的文章,都是该blog博主Caoer(草儿)原创,凡是索引、收藏
、转载请注明来处和原文作者。非常感谢。