jQuery.ajax() for ASP.NET Developers
By Tim Pollock, OCI Senior Software Engineer
June 2013
Introduction
Visual Studio is a great tool for developing web applications, but your site might end up being rather sluggish if you don't design it to avoid large data transfers (postbacks) when pages are updated. Often, a developer will be tasked with solving this problem on an existing site and will not have the option of a major rewrite. This article shows how to modify an existing ASP.NET site to send much less data across the wire, and thus provide a more performant interface to its users. It also provides guidance to those creating a new ASP.NET site.
The focus of this article is to show how you can use jQuery and ajax() to greatly reduce the amount of data retrieved from the server when the user clicks a control on a page. By using ajax(), just the data necessary to update the page is retrieved, resulting in a faster response for the user.
An example application that demonstrates the methods discussed in this article is available for download here.
Full Postback
Every click on a control in an ASP.NET page will result in a full postback of the entire page if nothing has been done to optimize those actions. This might result in a very large amount of data being transferred from the server to the client on every click. For instance, the examples that accompany this article have a page containing two GridView controls, with the second one containing details of the selected row of the first. Clicking a row in the first GridView results in new data being retrieved for the second GridView. Clicking a row will result in a full postback, including the data for the first GridView, even though that data did not change. This results in an unnecessary amount of data transfer. Ideally, just the page data that is changed should be transferred.
The sample application for this article has a FullPostback page that demonstrates this. The size of the data transfer is shown in the Developer Tools section at the bottom of each image shown below. (You can open Developer Tools in the Chrome browser by clicking F12.)
The image above on the left shows that clicking the Load Users button results in a full postback of 4.5 KB (3.2 KB for the page and 1.3 KB for the stylesheet, as shown below).
The image above on the right shows that clicking a row in the Users GridView control results in a full postback of 5.6 KB (4.3 KB for the page and 1.3 KB for the stylesheet, as shown below).
The difference in size between the page with no user selected and a user selected is 4.3 - 3.2 = 1.1 KB. Assuming that 1.1 KB is the size of the data in the User Details table, it would be better if just that amount of data would be transferred when a user clicks on the Users GridView. A common way to reduce that size is to use the ASP.NET UpdatePanel control to refresh just the portion of the screen that changes. The next section shows how that is done.
Partial Postback using UpdatePanel
The ASP.NET UpdatePanel control allows the developer to limit a page refresh to just a portion of the page. For instance, a button and a GridView control might be wrapped in an UpdatePanel so that only those two controls are refreshed. The UpdatePanel page of the sample application shows how that is done.
Before using an UpdatePanel, a ScriptManager must be defined in the aspx file. That is done by placing the following code somewhere near the top of the controls within the tag:
<asp:ScriptManager ID="scriptManager" runat="server"></asp:ScriptManager>
Then, somewhere below the ScriptManager definition, the controls to be grouped are wrapped with an UpdatePanel:
<asp:UpdatePanel runat="server" ID="updatePanel1">
<ContentTemplate>
<!-- Controls you want to isolate -->
</ContentTemplate>
</asp:UpdatePanel>
The UpdatePanel page is identical to the FullPostback page, except that the Load Users button and the two GridView controls are wrapped in an UpdatePanel.
The image above on the left shows that clicking the Load Users button results in a partial postback of 2.4 KB, as shown below.
The image above on the right shows that clicking a row in the Users GridView control results in a partial postback of 3.6 KB, as shown below.
So, for this example application, using an UpdatePanel reduced the data transfer when clicking a row in the Users GridView by 5.6 - 3.6 = 2.0 KB. In an actual application you would probably have more controls on the page that would be outside of the UpdatePanel, and the data associated with those controls would not be included in the partial postback. If that were the case, the difference between a full and partial postback would be greater. There is still, however, much more data being transferred than is necessary.
ASP.NET controls and not eliminated by the use of UpdatePanel. That data is passed as part of the postback, and contributes significantly to the size of the data being transferred. For instance, clicking on a user in the GridView results in the server sending the user details, along with the following ViewState data:
/wEPDwULLTEwNTc4OTU4OTAPZBYCAgMPZBYCAgMPZBYCZg9kFgQCAw8WAh4HVmlzaWJsZWcWAgIBDzwrABECAA8WBB4LXyFEYXRhQm
91bmRnHgtfIUl0ZW1Db3VudAIDZAwUKwAEFggeBE5hbWUFAklEHgpJc1JlYWRPbmx5aB4EVHlwZRkrAh4JRGF0YUZpZWxkBQJJRBYI
HwMFCFVzZXJuYW1lHwRoHwUZKwIfBgUIVXNlcm5hbWUWCB8DBQpGaXJzdCBOYW1lHwRoHwUZKwIfBgUKRmlyc3QgTmFtZRYIHwMFCU
xhc3QgTmFtZR8EaB8FGSsCHwYFCUxhc3QgTmFtZRYCZg9kFgpmD2QWAgIBDw8WAh8AaGRkAgEPZBYIAgEPDxYEHgRUZXh0BQExHwBo
ZGQCAg8PFgIfBwUGc3NtaXRoZGQCAw8PFgIfBwUDU2FtZGQCBA8PFgIfBwUFU21pdGhkZAICD2QWCAIBDw8WBB8HBQEyHwBoZGQCAg
8PFgIfBwUGYmJyb3duZGQCAw8PFgIfBwUEQmlsbGRkAgQPDxYCHwcFBUJyb3duZGQCAw9kFggCAQ8PFgQfBwUBMx8AaGRkAgIPDxYC
HwcFBGFhbnRkZAIDDw8WAh8HBQRBZGFtZGQCBA8PFgIfBwUDQW50ZGQCBA8PFgIfAGhkZAIFD2QWAgIBDzwrABEAZBgCBRNncmlkVm
lld1VzZXJEZXRhaWxzD2dkBQ1ncmlkVmlld1VzZXJzDzwrAAwBCAIBZAd3OCiJnsqdo26ouHYmjru5jJEt9aENO9MlA6XK8Gao
We might be able to disable ViewState for some of the controls, but not all. If, instead, we use jQuery.ajax(), the only data that is transferred when clicking on the Users GridView would be the data associated with the user. The next section shows how to implement this technique.
Using ajax() and ASP.NET Controls
If we use jQuery.ajax() instead of the ASP.NET UpdatePanel control, we will eliminate the ViewState as well as other overhead. An ajax() call will return just the requested data, which is exactly what we want.
Modifying an ASP.NET page to use jQuery.ajax requires replacing the control handlers with client-side jQuery functions and registering them to handle click events. It also involves adding server-side methods that the jQuery functions call to get the required data for populating the ASP.NET controls.
Client-Side - Handling ASP.NET Control Events
We can use jQuery to define a click handler for a button to replace the An asp:Button OnClick handler, as shown below:
$("#btnLoadUsers").click(function () {
// Handle a button click
});
vAnd a asp:GridView row click handler can be replaced as follows:
$("#gridViewUsers").delegate('tr', 'click', function () {
// Handle a GridView row click
});
The comment lines in the examples above show where you would add the ajax() calls to get the data from the server.
Server-Side - Methods to Return Data
The code behind for handling the click events would need to be replaced with methods that simply return the requested data, such as the following:
[WebMethod]
public static List<UserData.UserInfo> GetUsers()
{
return UserData.GetUsers();
}
[WebMethod]
public static UserData.UserDetails GetUserDetails(string id)
{
return UserData.GetUserDetails(id);
}
Note that the methods need to be defined as public static
, and decorated with the [WebMethod]
attribute. The GetUserDetails() WebMethod shown above returns an object that defines a user. The UserDetails class returned by the WebMethod looks like this:
public class UserDetails
{
public int ID;
public UserInfo Info;
public string Address;
public string City;
public string State;
public string PostalCode;
}
The UserDetails class contains a UserInfo member, which looks like this:
public class UserInfo
{
public int ID;
public string UserName;
public string FirstName;
public string LastName;
}
That UsersDetails object will be sent back to the client as a JSON string defining the user.
Client-Side - Using ajax() to Call Data Methods
In order to use those WebMethods, define an ajax() call in your client code and specify the WebMethod name in the url:
argument. The ajax() call to the GetUserDetails() WebMethod is shown below:
$("#gridViewUsers").delegate('tr', 'click', function () {
var req = $.ajax({
type: "POST",
url: "AspAjax.aspx/GetUserDetails",
contentType: "application/json; charset=utf-8",
dataType: "json",
data: "{ id: " + id + "}"
});
req.done( /* Add function here to handle success case */ );
req.fail( /* Add function here to handle error case */ );
});
Any parameters that are passed to a WebMethod are defined in the "data:" argument to the ajax() call. If the WebMethod takes no parameters, then use data:"{}"
.
Note that the ajax() call allows us to define functions to be executed when the call is done and when it fails. The done()
function defines how to process the data returned by the WebMethod. The fail()
function defines how the web application should handle an unsuccessful ajax() call.
Also note that you can use the success()
and error()
ajax() parameters. There are also success()
and error()
functions, but they have been deprecated. The first usage shown below is a success()
function, and has been deprecated. The second usage below is the success()
parameter to the ajax() function, and can be used instead of the done()
function.
$.ajax({}).success(function(){...});
$.ajax({
success: function(){...}
});
In the example shown above an HTTP POST request method was used. That is the default, but the next section shows how to do the same with an HTTP GET request.
Client-Side - Making an HTTP GET jQuery.ajax() Request
There are times when we need to make a GET request instead of a POST request. Using a GET request might result in a slightly faster response than a POST because the latter has an additional Content-Length line in the header. A GET request is also more in line with REST guidelines for cases where you're just getting data from the server. POST requests, however, have the advantage of keeping the request data out of the URL, thus hiding it from the user and from URL indexing services.
To make a GET request you need to add the following attribute to your WebMethod:
[ScriptMethod(UseHttpGet = true, ResponseFormat = ResponseFormat.Json)]
You'll also need to make the following modification to the data:
line in your jQuery.ajax() call:
data: "id=" + id
After those changes you can specify that the ajax() call should use the GET method by modifying the type:
line in your jQuery.ajax() call:
type: "GET",
Client-Side - Examining the Server Response
The response from the UserDetails() WebMethod is a JSON string containing the requested data:
{"d":
{
"__type":"DataSource.UserData+UserDetails",
"ID":2,
"Info":
{
"__type":"DataSource.UserData+UserInfo",
"ID":2,
"UserName":"bbrown",
"FirstName":"Bill",
"LastName":"Brown"
},
"Address":"6787 Spring St.",
"City":"Dallas",
"State":"TX",
"PostalCode":"85858"
}
}
You can see that the JSON string contains all of the members of the UserDetails object, including its UserInfo member.
Note also that the JSON string returned by a WebMethod contains one top-level name/value pair named d
. Before .NET 3.5, just the value string would be returned, but that was a security threat, since executable javascript could be injected by a malicious party. That javascript would then be executed in the browser. Changing the form of the json string to {d:object}
resolved that, since that is not valid javascript.
Next we'll look at how to parse that JSON data to populate ASP.NET controls on the page.
Client-Side - Using the WebMethod Response
The JSON data returned by the WebMethod can be easily accessed. If you look at it in the Developer Tools section you can see the return values:
The individual data items can be accessed as shown in the following examples:
var userName = data.d.Info.UserName;
var address = data.d.Address;
To set the User Details GridView, you can directly manipulate the HTML that the GridView generates. This involves clearing out all but the first (header) row, then appending HTML to the GridView. The code for the done()
function is as follows:
req.done(function (data) {
$("#gridViewUserDetails tr:not(:first-child)").html("");
$("#gridViewUserDetails").append(
"<tr><td>UserName:</td><td>" + data.d.Info.UserName + "</td></tr>"
+ "<tr><td>First Name:</td><td>" + data.d.Info.FirstName + "</td></tr>"
+ "<tr><td>Last Name:</td><td>" + data.d.Info.LastName + "</td></tr>"
+ "<tr><td>Address:</td><td>" + data.d.Address + "</td></tr>"
+ "<tr><td>City:</td><td>" + data.d.City + "</td></tr>"
+ "<tr><td>State:</td><td>" + data.d.State + "</td></tr>"
+ "<tr><td>Zip:</td><td>" + data.d.PostalCode + "</td></tr>");
});
Client-Side - Examining ajax() Response Size
When using ajax(), the data transfer is drastically reduced. The two images below show the amount of data transferred when initially loading the user table and when clicking a row in that table to load user details.
The image above on the left shows that clicking the Load Users button results in a data transfer of 575 bytes, as shown below.
The image above on the right shows that clicking a row in the Users GridView control results in a transfer of 501 bytes, as shown below.
So, by using ajax() the size of the data transferred each time a row in the Users GridView is clicked is reduced from 5.6 KB to just 501 bytes. That's a significant decrease in data which should result in a similar improvement in performance.
Replacing ASP.NET Controls with HTML
You might find, while you're refactoring your code, that it makes sense to replace some of the ASP.NET controls with the corresponding HTML tags they render. The JQueryAjax page of the example application does that, replacing the asp:Button and asp:GridView controls with HTML input and table tags.
That example page defines the table structure for the Users table, but leaves the table body empty so it can be filled when the data arrives:
<div id="userTable" style="display:none;">
<h3>Users</h3>
<table id="tblUsers" class="users_table">
<thead>
<tr>
<th style="width:100px">Username</th>
<th style="width:100px">First Name</th>
<th style="width:100px">Last Name</th>
<th style="visibility:hidden"></th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
The done()
function that is called when the GetUsers() WebMethod returns uses the jQuery templating plugin (see references at the end of this article). In the JQueryAjax.js file, the template is used to add a table row for each record returned by GetUsers():
req.done(function (data) {
var table = $("#tblUsers tbody");
table.html('');
$("#userRowTemplate").tmpl(data.d).appendTo(table);
});
The template used in that done()
function is defined in the JQueryAjax.aspx file. The data passed to the template function contains the basic information for the user, along with an ID that is used to look up user details when a row is clicked. The template is shown below:
<script id="userRowTemplate" type="text/x-jQuery-tmpl">
<tr>
<td>${UserName}</td>
<td>${FirstName}</td>
<td>${LastName}</td>
<td style="visibility:hidden">${ID}</td>
</tr>
</script>
In other cases it might be better to retain the ASP.NET controls, especially in the case of the more complex ones, such as the Login or AdRotator controls. You might also have custom controls that you don't want to replace. It only makes sense to replace ASP.NET controls if doing so provides some performance benefit or simplifies your code. The focus should be on reducing postback-related data transfers, and jQuery.ajax() can do that for you.
Another option is to define just the bare minimum of HTML controls on the page and retrieve the remaining controls with ajax() calls. The AjaxGetHtml page of the example application demonstrates this. The page the user initially loads has just a button
and two div
tags, one for the users table and one for user details. The WebMethods that are called by ajax() prepare the HTML that go into those div
tags, and the jQuery done()
methods that handle the returning data just plug it into the page.
Here's the HTML for that example page:
<body>
<form id="form1" runat="server">
<p><a href="Default.aspx">Home</a></p>
<button id="btnLoadUsers" type="button">Load Users</button>
<div id="userTableContainer" style="display:none;"></div>
<div id="userDetailsContainer" style="display:none;"></div>
</form>
</body>
The method to handle the button click makes an ajax() call with a DataType of "html":
var req = $.ajax({
type: "GET",
url: "AjaxGetHtml.aspx/GetUserDetails",
contentType: "application/json; charset=utf-8",
dataType: "html",
data: "id=" + id
});
The GetUsers() WebMethod returns a string defining the HTML to be put into the Users div
:
[WebMethod]
[ScriptMethod(UseHttpGet = true, ResponseFormat = ResponseFormat.Xml)]
public static string GetUsers()
{
var sb = new System.Text.StringBuilder();
sb.Append(@"<h3>Users</h3>");
sb.Append(@"<table id=""tblUsers"" class=""users_table"">");
sb.Append(@"<thead><tr>");
sb.Append(@"<th style=""width:100px"">Username</th>");
sb.Append(@"<th style=""width:100px"">Firstname</th>");
sb.Append(@"<th style=""width:100px"">Lastname</th>");
sb.Append(@"<th style=""visibility:hidden""></th>");
sb.Append(@"</tr></thead>");
var users = UserData.GetUsers();
foreach (var user in users)
{
sb.Append(@"<tr><td>");
sb.Append(user.UserName);
sb.Append(@"</td><td>");
sb.Append(user.FirstName);
sb.Append(@"</td><td>");
sb.Append(user.LastName);
sb.Append(@"</td><td style=""visibility:hidden"">");
sb.Append(user.ID);
sb.Append(@"</td></tr>");
}
return sb.ToString();
}
The HTML string that is returned to the page is simply added to the Users div
:
req.done(function (data) {
$("#userTableContainer").append(data);
In a real-world application we probably wouldn't send the html on every click, since sending just the data as a JSON string is more efficient. There might be cases, however, where that would make sense. For instance, suppose we had a page that presented several different complex views, but we knew that a user generally opened just one of them. In that case we might provide an empty div
for each of those views and fill out the ones the user chooses to visit. Instead of sending the html for an entire div
each time one of them needs to be updated, however, we might check if the div
is empty and only send the html the first time. Subsequent updates could then use jQuery.ajax() to get a JSON string containing just the data necessary to update the controls in the div
. That would minimize the initial page size and provide the most efficient updates, giving the user the experience you want them to have when visiting the site.
Summary
This article showed how easy it is to change the way an ASP.NET web application requests data and updates controls in order to increase the responsiveness a site. The simple example reduced the postback size from 5.6 KB to just 501 bytes. A more complex application would likely see even larger decreases. If you're familiar with developing ASP.NET web apps you can use the jQuery.ajax() methods demonstrated in this article to significantly improve the performance of new and existing sites while retaining the technologies you're familiar with.
References
- [1] jQuery
- [2] jQuery.ajax()
- [3] jQuery Templating Plugin
- [4] JSON
- [5] ScriptMethod Attribute
- [6] ScriptManager Class