Advertisement

ASP.NET and AJAX

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

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 on Timer.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> tag, 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:

Thanks for reading!

Advertisement