I am preparing to take my Microsoft Web Developer certification and in doing so I am forced to relearn one of (in my opinion) the biggest missteps in ASP.NET history; the “Update Panel”. Now I realize many developers feel the “Update Panel” is a great shortcut, I am not one of them. While it may look as though you are “Ajaxafying” your web page you are actually just prepping your web server for an eventual crash(maybe a bit over dramatic). Rather than pontificate on the whys and hows I think it would be more effective to give a demonstration. I will start from best to worst; I will use a simple example of an ajax zip code lookup:
Example 1:
Let’s look at the numbers for an pure JSON/AJAX implementation of the lookup, using ASP.MVC :
Initial Page Size:
4513 Bytes
Lookup Request Length:
15 bytes
Request Body:
{ 'zip': '08021'}
Lookup Response Length:
52 bytes
Response Body:
{"Zip":"08021","City":"Laurel Springs","State":"NJ"}
All and all this is a very small amount of data being sent back and forth. There is very little in the way of overhead in these calls. The implementation is as follows:
Important Controls:
<div class="editor-label">
@Html.LabelFor(model => model.City)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.City)
@Html.ValidationMessageFor(model => model.City)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.State)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.State)
@Html.ValidationMessageFor(model => model.State)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.Zip)
</div>
<div class="editor-field">
@Html.EditorFor(model => model.Zip)
@Html.ValidationMessageFor(model => model.Zip)
<input type="button" onclick="return getData();" value="Update City/State" />
</div>
Script:
function getData() {
var zip = $("#Zip").val();
$.ajax(
{ type: "POST",
url: "@(Url.Content("~/Contact/_getZipData"))",
data: "{'zip':'" + zip + "'}",
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function(data){
$("#City").val(data.City);
$("#State").val(data.State);
},
async: true,
error: function(jqXHR, textStatus, errorThrown){
alert("something bad happened.");
}
});
return false;
}
Controller:
public class ContactController : Controller
{
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult _getZipData(string zip)
{
ZipCode zipCodeData = zip.GetCityAndState();
return Json(zipCodeData);
}
}
Example 2:
The following is a WCF AJAX enabled service. In the world of ASP.NET this is the optimal method for the backend of an Ajax service. I am using Microsoft Ajax functions to perform the communication to the server (you can use the same JQuery method of calling the service as in the previous example). This is where we will notice some inefficiencies occur:
Initial Page Size:
5697 Bytes
Lookup Request Length:
15 bytes
Request Body:
{"zip":"08021"}
Lookup Response Length:
100 bytes
Response Body:
{"d":{"__type":"ZipCode:#MvcApplication1.Cache","City":"Laurel Springs","State":"NJ","Zip":"08021"}}
Important Controls:
<div class="editor-label">
<asp:Label ID="lbCity" runat="server" Text="City" AssociatedControlID="tbCity"></asp:Label>
</div>
<div class="editor-field">
<asp:TextBox ID="tbCity" ClientIDMode="Static" runat="server"></asp:TextBox>
</div>
<div class="editor-label">
<asp:Label ID="lbState" runat="server" Text="State" AssociatedControlID="tbState"></asp:Label>
</div>
<div class="editor-field">
<asp:TextBox ID="tbState" ClientIDMode="Static" runat="server"></asp:TextBox>
</div>
<div class="editor-label">
<asp:Label ID="lbZip" runat="server" Text="Zip" AssociatedControlID="tbZip"></asp:Label>
</div>
<div class="editor-field">
<asp:TextBox ID="tbZip" ClientIDMode="Static" runat="server"></asp:TextBox>
<input type="button" onclick="return getData();" value="Update City/State" />
</div>
Script:
function getData() {
var zip = $("#tbZip").val();
GetZipData.GetZipInfo(zip, onSucceed, onError);
return false;
}
function onSucceed(result) {
$("#tbCity").val(result.City);
$("#tbState").val(result.State);
}
function onError(result) {
alert("something bad happened.");
}
Code Behind (I added the debug writes to show the page is not calling events in the Ajax call):
public partial class WcfMethodTest : System.Web.UI.Page
{
protected void Page_Init(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Page Init");
}
protected void Page_Load(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Page Load");
}
protected void Page_PreRender(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Page PreRender");
}
protected void Page_Unload(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Page Unload");
}
}
Service:
[ServiceContract(Namespace = "")]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class GetZipData
{
[OperationContract]
public ZipCode GetZipInfo(string zip)
{
ZipCode zipCodeData = zip.GetCityAndState();
return zipCodeData;
}
}
Example 3:
This is using the Microsoft AJAX JS libraries to call a Page method. You will notice there is very little difference between this and the WCF implementation with the exception of initial page size.
Initial Page Size:
9337 Bytes (a lot of scaffolding needed for this implementation)
Lookup Request Length:
15 bytes
Request Body:
{"zip":"08021"}
Lookup Response Length:
99 bytes
Response Body:
{"d":{"__type":"MvcApplication1.Cache.ZipCode","Zip":"08021","City":"Laurel Springs","State":"NJ"}}
Important Controls:
<div class="editor-field">
<asp:TextBox ID="tbCity" ClientIDMode="Static" runat="server"></asp:TextBox>
</div>
<div class="editor-label">
<asp:Label ID="lbState" runat="server" Text="State" AssociatedControlID="tbState"></asp:Label>
</div>
<div class="editor-field">
<asp:TextBox ID="tbState" ClientIDMode="Static" runat="server"></asp:TextBox>
</div>
<div class="editor-label">
<asp:Label ID="lbZip" runat="server" Text="Zip" AssociatedControlID="tbZip"></asp:Label>
</div>
<div class="editor-field">
<asp:TextBox ID="tbZip" ClientIDMode="Static" runat="server"></asp:TextBox>
<input type="button" onclick="return getData();" value="Update City/State" />
</div>
Script:
function getData() {
var zip = $("#tbZip").val();
PageMethods.GetZipData(zip, onSucceed, onError);
return false;
}
function onSucceed(result) {
$("#tbCity").val(result.City);
$("#tbState").val(result.State);
}
function onError(result) {
alert("something bad happened.");
}
Code Behind (I added the debug writes to show the page is not calling events in the Ajax call):
public partial class PageMethodTest : System.Web.UI.Page
{
protected void Page_Init(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Page Init");
}
protected void Page_Load(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Page Load");
}
protected void Page_PreRender(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Page PreRender");
}
protected void Page_Unload(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Page Unload");
}
[WebMethod()]
public static ZipCode GetZipData(string zip)
{
ZipCode zipCodeData = zip.GetCityAndState();
return zipCodeData;
}
}
Example 4:
This is using the Update Panel to update the controls that should be updated in the same manner as the previous examples. This is by far the worst of all of the examples posted
Initial Page Size:
5538 Bytes (less than the previous example; less scaffolding required)
Lookup Request Length:
638 bytes (over a 4000% increase over previous examples!!!)
Request Body:
ctl00%24body%24smUpdate=ctl00%24body%24smUpdate%7Cctl00%24body%24lbUpdateCityState&__EVENTTARGET=&__EVENTARGUMENT=&__VIEWSTATE=%2FwEPDwUKLTE0ODYwOTIxMWRkXgPL66ERIqtx3gVubvTQQrkVqfAVcjgDjOwCDEs4Z8k%3D&__EVENTVALIDATION=%2FwEWCwKC9ZT3DAK59tu9AgL%2F1ZrTAgLIuYntCwKZ9eKsCgKp742yBwLVs6yZCQKqxfb2DALOw%2ByZAwKu2fbCAQL%2FlNACnJEvyD3m7WcENitn%2FCghxG8SBhyLx5ZsHpmAJLC57q4%3D&ctl00%24body%24tbFirstName=&ctl00%24body%24tbLastName=&ctl00%24body%24tbStreetAddress1=&ctl00%24body%24tbStreetAddress2=&ctl00%24body%24tbCity=&ctl00%24body%24tbState=&ctl00%24body%24tbZip=08021&ctl00%24body%24tbPhone1=&ctl00%24body%24tbPhone2=&__ASYNCPOST=true&ctl00%24body%24lbUpdateCityState=Update%20City%2FState
Lookup Response Length:
1307 bytes (over a 2500% increase over the pure JSON implementation and over a 1300% increase over the WCF and Page Method examples!!!)
Response Body:
1|#||4|647|updatePanel|body_upUpdateCityState|
<div class="editor-label">
<label for="body_tbCity" id="body_lbCity">City</label>
</div>
<div class="editor-field">
<input name="ctl00$body$tbCity" type="text" value="Laurel Springs" id="body_tbCity" />
</div>
<div class="editor-label">
<label for="body_tbState" id="body_lbState">State</label>
</div>
<div class="editor-field">
<input name="ctl00$body$tbState" type="text" value="NJ" id="body_tbState" />
</div>
|68|hiddenField|__VIEWSTATE|/wEPDwUKLTE0ODYwOTIxMWRkXgPL66ERIqtx3gVubvTQQrkVqfAVcjgDjOwCDEs4Z8k=|136|hiddenField| __EVENTVALIDATION|/wEWCwKC9ZT3DAKp742yBwLVs6yZCQK59tu9AgL/1ZrTAgLIuYntCwKZ9eKsCgKqxfb2DALOw+yZAwKu2fbCAQL/lNACjqb cQj2LhjBHPYGxcc/Oy+TYZxbWVj4w/u27ZVUFn84=|51|asyncPostBackControlIDs||ctl00$body$lbUpdateCityState,body_lbUpdateCityState|0|postBackControlIDs|||52|updatePanelIDs ||fctl00$body$upUpdateCityState,body_upUpdateCityState|0|childUpdatePanelIDs|||51|panelsToRefreshIDs ||ctl00$body$upUpdateCityState,body_upUpdateCityState|2|asyncPostBackTimeout||90|11|formAction ||UpdatePanel|4|pageTitle||Test|
As a side note: There is also a hidden issue to the Update Panel, the page must go through the entire page life cycle. Meaning every event in the page is fired including all of the rendering and child creation events, even if the control is not updated.
Important Controls:
<asp:UpdatePanel ID="upUpdateCityState" runat="server" UpdateMode="Conditional" ChildrenAsTriggers="false">
<Triggers>
<asp:AsyncPostBackTrigger ControlID="lbUpdateCityState" EventName="Click"/>
</Triggers>
<ContentTemplate>
<div class="editor-label">
<asp:Label ID="lbCity" runat="server" Text="City" AssociatedControlID="tbCity"></asp:Label>
</div>
<div class="editor-field">
<asp:TextBox ID="tbCity" runat="server"></asp:TextBox>
</div>
<div class="editor-label">
<asp:Label ID="lbState" runat="server" Text="State" AssociatedControlID="tbState"></asp:Label>
</div>
<div class="editor-field">
<asp:TextBox ID="tbState" runat="server"></asp:TextBox>
</div>
</ContentTemplate>
</asp:UpdatePanel>
<div class="editor-label">
<asp:Label ID="lbZip" runat="server" Text="Zip" AssociatedControlID="tbZip"></asp:Label>
</div>
<div class="editor-field">
<asp:TextBox ID="tbZip" runat="server"></asp:TextBox>
<asp:Button ID="lbUpdateCityState" runat="server" Text="Update City/State" onclick="lbUpdateCityState_Click"></asp:Button>
</div>
Code Behind (I added the debug writes to show the page is calling every event in the Update Panel call):
public partial class UpdatePanelTest : System.Web.UI.Page
{
protected void Page_Init(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Page Init");
}
protected void Page_Load(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Page Load");
}
protected void Page_PreRender(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Page PreRender");
}
protected void Page_Unload(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Page Unload");
}
protected void lbUpdateCityState_Click(object sender, EventArgs e)
{
string zip = tbZip.Text;
ZipCode zipCodeData = zip.GetCityAndState();
tbCity.Text = zipCodeData.City;
tbState.Text = zipCodeData.State;
}
}
While this example is fairly simplistic it illustrates a very important point, the update panel adds a tremendous amount of overhead for a minor saving on code. Is it more maintainable? Slightly; as new developers only need to know ASP.NET and not javascript (IMO not an options for web developers today).
So let’s look at the numbers:
|
|
Pure Ajax Json
|
WCF ASP.NET
|
Page Method ASP.NET
|
Update Panel
|
|
Request Length
|
15
|
15
|
15
|
638
|
|
Response Length
|
52
|
100
|
99
|
1307
|
|
Lines of Script
|
18
|
12
|
12
|
0
|
|
Lines of Markup
|
21
|
18
|
18
|
25
|
|
Lines of Code (just the update method)
|
5
|
10
|
5
|
7
|


I did not add execution times because as can vary, but the raw numbers speak for themselves. There is very little value add in using the Update panel over standard AJAX calls. I would go as far as saying there is no room for them in any web application/site in which load based performance is any kind of consideration.
UpdatePanelComparisons.zip (2.49 mb)