In this tutorial, we'll take a look at some of the things you can do with ASP.NET and AJAX in your web applications. It's more than just wrapping an UpdatePanel
around some buttons, textboxes and grids!
Asynchronous JavaScript and XML
There are many caveats with arbitrarily dropping UpdatePanels onto webforms and hoping for the best.
Though this tutorial will focus primarily on other components besides the UpdatePanel, it might be useful to take a look at postback triggers as well. Wrapping some controls on a webform in an UpdatePanel is a cheap and cheerful way of implementing Ajax.
Postbacks caused by the web controls in the UpdatePanel should happen asynchronously and not cause an entire page postback. There are, however, many caveats with arbitrarily dropping UpdatePanels onto webforms and hoping for the best.
There are also situations in which one may want to conditionally cause a postback of the entire page, or perhaps just make one method call to a backend method or web service to update some small part of the page.
UpdatePanel
An UpdatePanel control specifies what regions of a page can be updated asynchronously.
Let's start by looking at a simple UpdatePanel control and some of the things it can do out of the box. The control specifies what regions of a page can be updated asynchronously, and thus not require an entire postback of the page.
Create a new ASP.NET Web Application project. To your default.aspx
page, add a ScriptManager
control, a TextBox
control called txtOutsideUpdatePanel
, and an UpdatePanel. Add a ContentTemplate
to the UpdatePanel, and, within it, add a Button control called btnInsideUpdatePanel
, and a TextBox
control called txtInsideUpdatePanel
. Below are the salient lines from the source view:
<div> <asp:ScriptManager ID="ScriptManager1" runat="server"> </asp:ScriptManager> <asp:TextBox ID="txtOutsideUpdatePanel" runat="server"></asp:TextBox> <asp:UpdatePanel ID="UpdatePanel1" runat="server"> <ContentTemplate> <asp:Button runat="server" Text="Update" ID="btnInsideUpdatePanel" /> <asp:TextBox runat="server" ID="txtInsideUpdatePanel"></asp:TextBox> </ContentTemplate> </asp:UpdatePanel> </div>
Next, add the following code to the code-behind for the page (or to the script tag if you're not using the code-behind):
Public Class _Default Inherits System.Web.UI.Page Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load txtOutsideUpdatePanel.Text = Now.ToString End Sub Protected Sub btnInsideUpdatePanel_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btnInsideUpdatePanel.Click txtInsideUpdatePanel.Text = Now.ToString End Sub End Class
View the page in your web browser, and you should see two textboxes on the webform. The first textbox should have a date and time in it. If you refresh the page, the first textbox should update its date and time. Press the button, and only the second textbox should refresh its date and time. Thus the button is causing an asynchronous postbox, because it's within the UpdatePanel.
What we've done so far is the easy way of Ajax'ifying a webform. We could easily put an entire grid with paging support within the UpdatePanel for flicker-less paging.
Let's look at all this in a little more detail.
Controlling UpdatePanel Updates
We can control when the UpdatePanel control posts back based on events that occur to controls both inside and outside the panel itself. Here is the properties window:
There are three properties of interest to us at present:
- UpdateMode: Always (default) or Conditional
- ChildrenAsTriggers: True (default) or False
- Triggers: A collection of controls - discussed further below
There are three valid combinations of UpdateMode
and ChildrenAsTriggers
:
- Always =True UpdatePanel will refresh when the whole page refreshes, or when a child control posts back.
- Always = False (invalid)
- Conditional = True UpdatePanel will refresh when the whole page refreshes, or when a child control posts back, or a trigger from outside of the UpdatePanel causes a refresh.
- Conditional = False UpdatePanel will refresh when the whole page refreshes, or a trigger outside of the UpdatePanel causes a refresh. A child control will not cause a refresh.
The next property we're interested in is the Triggers
property, which comes in two flavours:
- AsyncPostBackTrigger: Causes an asynchronous refresh of the UpdatePanel
- PostBackTrigger: Causes a page postback by a child control of the UpdatePanel
Let's take a look at how these affect the functionality of the UpdatePanel. Paste the following code into a webform, and then the VB.Net code below that into the code-behind.
We have two buttons inside the panel and two buttons outside. We have wired up the triggers such that a button inside will cause a full page postback, and a button will cause an asynchronous refresh.
<form id="form1" runat="server"> <asp:ScriptManager ID="ScriptManager1" runat="server" /> <asp:UpdatePanel ID="UpdatePanel1" runat="server"> <Triggers> <asp:AsyncPostBackTrigger ControlID="btnAsyncTrigger" /> <asp:PostBackTrigger ControlID="btnPostBackTrigger" /> </Triggers> <ContentTemplate> <asp:Label ID="lblInnerTime" runat="server"></asp:Label> <br /> <asp:Button ID="btnInnerTime" runat="server" Text="Inner Time" /> <asp:Button ID="btnPostBackTrigger" runat="server" Text="PostBack Trigger" /> </ContentTemplate> </asp:UpdatePanel> <br /> <br /> <asp:Label ID="lblOuterTime" runat="server"></asp:Label> <br /> <asp:Button ID="btnOuterTime" runat="server" Text="Outer Time" /> <asp:Button ID="btnAsyncTrigger" runat="server" Text="Async Trigger" /> </form>
Code-behind:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) lblInnerTime.Text = Now lblOuterTime.Text = Now End Sub
The webform should look as follows:
Clicking the Inner Time
button will cause an asynchronous postback. This is expected, as the button is within the UpdatePanel. Clicking the Outer Time
button will cause a full page postback. Again, this is expected, as the Outer Time button is outside the panel.
The two interesting cases are the PostBack
trigger and Async Trigger
buttons, to which we make reference in the triggers section of the UpdatePanel. When defining triggers, we need to specify the ControlID of the control acting as a trigger, and optionally the event for which the trigger should fire. If we omit the event, it will fire on the default event for that control.
Conditional UpdatePanel Updates
By setting the UpdateMode
property of the UpdatePanel to Conditional
, and ChildrenAsTriggers
to False
we can control when updates will be performed. An asynchronous postback will still be performed, but we can decide when to send the updated HTML content for that region of a page to the browser.
Paste the following code into an ASPX page:
<form id="form1" runat="server"> <div> <asp:ScriptManager ID="ScriptManager1" runat="server"> </asp:ScriptManager> <asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional" ChildrenAsTriggers="False" > <ContentTemplate> <asp:Label ID="lblDateTime" runat="server" Text=""></asp:Label><br /> <asp:Button ID="btnAsyncPostBack1" runat="server" Text="Inside UpdatePanel 1" /> <asp:Button ID="btnAsyncPostBack2" runat="server" Text="Inside UpdatePanel 2" /> </ContentTemplate> </asp:UpdatePanel> <br /> <asp:Button ID="btnSyncPostBack" runat="server" Text="Outside UpdatePanel" /> </div> </form>
And the following code into its code behind:
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load lblDateTime.Text = Now End Sub Protected Sub btnAsyncPostBack1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnAsyncPostBack1.Click 'Do Nothing End Sub Protected Sub btnAsyncPostBack2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnAsyncPostBack2.Click UpdatePanel1.Update() End Sub
You should get a form that looks like the following:
-
Clicking on the
Inside UpdatePanel 1
button will cause an asynchronous postback to occur, but the UpdatePanelwon't be updated. -
Clicking on the
Inside UpdatePanel 2
button will cause an asynchronous postback to occur, and we are explicitly updating the panel. - Clicking on the
Outside UpdatePanel
will cause a normal full page postback.
Timers
We can cause postbacks to occur periodically by using an ASP.NET timer control. This is useful for any regions of a webform that needs live/current data to be visible, such as news feeds or live stock numbers. The Timer.Tick
event is fired at an interval defined by the Interval
property, which is in milliseconds. It is the Tick
event that we can use to cause asynchronous or full page postbacks.
The way in which the timer
control influences the panel can be controlled using the Triggers
collection.
- As a child control of the UpdatePanel, with no triggers defined: refreshes asynchronously on
Timer.Tick
- Outside, with no triggers defined: Entire page posts back on
Timer.Tick
- As a child control, with a
PostBackTrigger
defined: Entire page posts back onTimer.Tick
- Outside, with an AsyncPostBackTrigger defined: UpdatePanel refreshes asynchronously on Timer.Tick
ASP.NET Ajax Client Library
When you add a
ScriptManager
control to a webform, it makes the ASP.NET Client Library JavaScript files available to the user's browser.
The JavaScript files are taken from the System.Web.Extensions
assembly. Visual Studio's Intellisense will also pick up the functionality exposed by the client library at design time.
Add a ScriptManager
to a webform, add a new <script> t
ag, type Sys.
and you should see a whole host of new bits and pieces to play with. We'll look at some of the namespaces exposed below.
The examples consist mostly of JavaScript code, which belongs in a <script>
tag.
Ajax Client Library Namespaces
- Global Namespace
Sys
Sys.Net
Sys.Serialization
Sys.Services
Sys.UI
Sys.WebForms
Global Namespace
The client library provides us with some extensions to existing JavaScript objects. The extensions should make working with JavaScript objects feel a little more like working with managed code. We can also very easily extend existing JavaScript objects ourselves. In addition to extending the functionality of JavaScript objects, the client library also automatically wires up a number of events that we can very easily hook into.
Arrays:
Here we are using the sort()
and join()
extension methods:
var unsortedArray = [5, 4, 3, 2, 1]; var sortedArray = unsortedArray.sort(); alert(sortedArray.join(','));
Here we are extending the Array
object by adding a min()
method:
function minElement() { var minimum = Infinity; for (var i = 0; i < this.length; i++) { if (this[i] < minimum) { minimum = this[i]; } } return minimum; } var myArray = [1, 2, 3, 4, 5, 6, 7, 8, 9]; Array.prototype.min = minElement; alert(myArray.min());
And here we are adding elements to an array:
var myArray1 = [1, 2, 3]; var myArray2 = [5, 6, 7]; Array.add(myArray1, 4); Array.addRange(myArray2, [8, 9]);
Sys.Debug
We can use the Sys.Debug.trace()
method to display messages in the debugger. This is useful if you want to avoid using alert()
calls all over your pages. The debugger messages appear in the Output window in Visual Studio during a debugging session. So this means that you need to "run" the web project and visit the page, or attach to an existing w3p process.
In the piece of code below, we have a simple loop which causes a divide by zero, which might cause issues in subsequent calculations. By using trace()
, we can print out the current value of the counter variable as the loop is running:
var counter = 10; while (counter >= 0) { counter -= 1; Sys.Debug.trace("Current value of counter = " + counter); var someCalculatedValue = 10 / counter; document.write(someCalculatedValue + " "); }
Now let's use it to help us design and test a new JavaScript object:
Employee = function(employeeId, name) { this.EmployeeId = employeeId; this.Name = name; } Employee.prototype = { toString: function () { return this.EmployeeId + " : " + this.Name; }, get_Name: function () { return this.Name; }, set_Name: function (newName) { this.Name = newName; } } Employee.registerClass("Employee"); var jamie = new Employee(123, "Jamie Plenderleith"); Sys.Debug.trace("Before name change : " + jamie.Name); jamie.Name = "Jamie Plenderleith Esq."; Sys.Debug.trace("After name change : " + jamie.Name);
Events
The client library wires up some page events that we can hook into easily. Page-specific events are as follows:
pageLoad
pageUnLoad
And then we can access some events wired up to the PageRequestManager
object which are related to asynchronous postbacks:
InitializeRequest
BeginRequest
PageLoading
PageLoaded
EndRequest
Let's use trace()
to see when these events fire:
<form id="form1" runat="server"> <div> <asp:ScriptManager ID="ScriptManager1" runat="server"> </asp:ScriptManager> <asp:UpdatePanel ID="UpdatePanel1" runat="server"> <ContentTemplate> <asp:Button ID="Button1" runat="server" Text="Button" /> </ContentTemplate> </asp:UpdatePanel> <script language="javascript" type="text/javascript"> var myPageRequestManager = Sys.WebForms.PageRequestManager.getInstance(); myPageRequestManager.add_initializeRequest(onInitializeRequest); myPageRequestManager.add_beginRequest(onBeginRequest); myPageRequestManager.add_pageLoading(onPageLoading); myPageRequestManager.add_pageLoaded(onPageLoaded); myPageRequestManager.add_endRequest(onEndRequest); function pageLoad(sender, args) { Sys.Debug.trace("pageLoad()"); } function onInitializeRequest(sender, args) { Sys.Debug.trace("PageRequestManager.InitializeRequest"); } function onBeginRequest(sender, args) { Sys.Debug.trace("PageRequestManager.BeginRequest"); } function onPageLoading(sender, args) { Sys.Debug.trace("PageRequestManager.PageLoading"); } function onPageLoaded(sender, args) { Sys.Debug.trace("PageRequestManager.PageLoaded"); } function onEndRequest(sender, args) { Sys.Debug.trace("PageRequestManager.EndRequest"); } function pageUnload(sender, args) { Sys.Debug.trace("pageUnload()"); } </script> </div> </form>
We could even cancel an asynchronous postback if we want to:
<form id="form1" runat="server"> <div> <asp:ScriptManager ID="ScriptManager1" runat="server"> </asp:ScriptManager> <asp:UpdatePanel ID="UpdatePanel1" runat="server"> <ContentTemplate> <asp:Button ID="btnUpdateDateTime" runat="server" Text="Update" /> <asp:Label ID="lblDateTime" runat="server" Text=""></asp:Label> </ContentTemplate> </asp:UpdatePanel> <script language="javascript" type="text/javascript"> var myPageRequestManager = Sys.WebForms.PageRequestManager.getInstance(); myPageRequestManager.add_initializeRequest(onInitializeRequest); function onInitializeRequest(sender, args) { var someCondition = false; if (!someCondition) { Sys.Debug.trace("someCondition=false. Aborting postback"); args.set_cancel(true); } } </script> </div> </form>
Remote Method Calls
If the user has a particularly large
ViewState
, this will cause a lot of extra overhead for both them and the webserver. The remote aspx page will go through nearly its complete lifecycle from loading through to unloading.
Now we'll take a look at making calls to specific remote methods. These exist entirely separate to the UpdatePanelcontrol, but they'll probably be used in conjunction in order to display the result of some method call.
When an asynchronous postback occurs within the control, a full postback of the page's ViewState
is sent to the webserver. So if the user has a particularly large ViewState
, this will cause a lot of extra overhead for both them and the webserver. In addition to the ViewState, the remote aspx page will go through nearly its complete lifecycle from loading through to unloading. We can interface with .NET 2.0 ASP.NET Web Services, .Net 4.0 WCF Services (which act like .Net 2.0 ASP.NET Web Services when using HTTP Transport anyway) and with ASP.NET WebForms PageMethods. We'll take a look at using PageMethods.
ASPX Page Methods
A Page Method is a public shared (static in C#) method defined in a webform decorated with System.Web.Services.WebMethod()
. In addition to decorating your methods appropriately, your ScriptManager
must have its EnablePageMethods
property set to True
. From there you should see the method available through the PageMethods
proxy class in JavaScript.
Here is a very simple example of a call to managed code to perform a calculation. Paste the following into the source view of a webform:
<form id="form1" runat="server"> <div> <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true"> </asp:ScriptManager> y = log <sub>b</sub>(x)<br /> Base b = <input id="txtBase" type="text" /><br /> Value x = <input id="txtValue" type="text" /><br /> Result y = <span id="lblResult"></span> <br /> <input id="btnCalculate" type="button" value="Calculate" /> <script language="javascript" type="text/javascript"> $addHandler($get('btnCalculate'), "click", btnCalculate_onclick); function btnCalculate_onclick() { PageMethods.CalculateLog($get('txtBase').value, $get('txtValue').value, calculateLog_Finished, calculateLog_Failed); } function calculateLog_Finished(result) { $get('lblResult').innerText = result; } function calculateLog_Failed(errObj) { Sys.Debug.trace(errObj.get_message()); } </script> </div> </form>
And to the code-behind file add the following code:
<System.Web.Services.WebMethod()> Public Shared Function CalculateLog(ByVal Value As Double, ByVal Base As Double) As Double Return Math.Log(Value, Base) End Function
You can see it's a very simple call to the Math.Log()
method, but it's executed asynchronously without requiring a postback, and without the use of an UpdatePanel. Observe the PageMethods.CalculateLog()
method call. It takes the two parameters required by the method in the code-behind. In the example above, the next parameter is the callback to execute upon successful completion of the method call, and the last parameter is the callback to execute when an error occurs.
A caveat on PageMethods
however: If the class does not appear for you in the JavaScript, you can try a few things:
- Ensure your code-behind method is
Public Shared
- Ensure there are no JavaScript syntax errors
- Ensure the ScriptManager's
EnablePageMethods
property = True - Remove and re-add the ScriptManager
- Perform a rebuild
A more complete syntax for a PageMethod method call is as follows:
PageMethods.MethodName(Param1, Param2 ... , ParamN, CompletionCallback, FailureCallback, ContextParameter)
Consider our original calculateLog_Finished()
method signature:
function calculateLog_Finished(result) {}
This method can take additional parameters:
function calculateLog_Finished(result, context, method) {}
-
Result
: The result of the method call -
Context
: The value of ContextParameter originally passed by the PageMethods proxy,
if any -
Method
: The method that was invoked - useful when sharing callbacks
Further Reading
There are a plethora of aspects related to Ajax that we haven't touched on. Nonetheless, the following may be of interest to you in your projects:
- jQuery
- Using jQuery to perform Ajax calls
- The Ajax Control Toolkit
- ASP.NET Ajax Website
- ASP.NET Ajax Videos
- Microsoft Ajax Overview
Thanks for reading!
Comments