How not to use ASP.AJAX - Pt 2

In my last post, I touched on the bloat that ASP.NET AJAX adds to your site, and ways of reducing the viewstate overhead for asynchronous calls.

This time round, I want to speak more about JSON and Web Services.

JSON (JavaScript Object Notation) is an extremely lightweight data structure that Web Services built in .NET can understand and interpret. Web Services are typically associated with XML calls, which although are verbose and readable, aren't much fun to create in Javascript and often end up hogging all your bandwidth.

JSON gets compressed into a single string, which gets passed to your web service that then constructs that string back into an object. For those living with in .NET 3.0 land out there, JSON is very similar to Object Initialisers.

So now we have a lightweight object notation that can communicate with Web Services built in .NET. Great. But how can we leverage these technologies to reduce the bloat of ASP.NET AJAX?

This all comes down to your architecture and how your pages get used. If you put a DataGrid within an UpdatePanel to achieve asynchronous everything, it's going to be difficult to replace that functionality with JSON and Web Service calls.

However, if you have a couple of text boxes and a submit button within an UpdatePanel that sends the text box values off to the server, chances are you can replace this with a JSON implementation. Consider this basic scenario:

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

    <title>Ajax Example</title>

<script runat="Server" language="C#">

    protected void lnkSubmit_Click(object sender, EventArgs e)

    {

        lblGreeting.Text = string.Format("Welcome: {0} {1}", txtFirstname.Text, txtLastname.Text);

        pnlInput.Visible = false;

        pnlOutput.Visible = true;

    }

</script>

</head>

<body>

    <form id="frmMain" runat="server">

    <asp:ScriptManager runat="server" />

    <div>

        <asp:UpdatePanel runat="Server" ID="upGreetMe">

            <ContentTemplate>

                <asp:Panel runat="server" ID="pnlInput" DefaultButton="lnkSubmit">

                    Firstname: <asp:TextBox runat="server" ID="txtFirstname"></asp:TextBox><br />

                    Lastname: <asp:TextBox runat="server" ID="txtLastname"></asp:TextBox><br />

                    <asp:LinkButton runat="server" ID="lnkSubmit" Text="Submit" OnClick="lnkSubmit_Click" />

                </asp:Panel>

                <asp:Panel runat="Server" ID="pnlOutput" Visible="False">

                    <asp:Label runat="server" ID="lblGreeting" />

                </asp:Panel>

            </ContentTemplate>

        </asp:UpdatePanel>   

    </div>

    </form>

</body>

</html>

Which would give something like the following:

ASP.NET AJAX Screen Output

That's an easy implementation, but how does it look on the wire?

Request:

ctl02=upGreetMe|lnkSubmit&__EVENTTARGET=lnkSubmit&__EVENTARGUMENT=&__VIEWSTATE=%2FwEPDwUKMjA3OTY1ODc0N2RkcEMKQ
DsHztIsqEaftQkNhQqa9L8%3D&txtFirstname=Andrew&txtLastname=den%20Hertog&__EVENTVALIDATION=%2FwEWBAKev73aCwLZh62
pDwKdlcWkAgKg%2BLK0C8JDoC%2F8jw%2BvvB1lvzQCJ%2B0MXZCK&

Response:

cc
177|updatePanel|upGreetMe|
               
                <div id="pnlOutput">
 
                    <span id="lblGreeting">Welcome: Andrew den Hertog</span>
               
</div>
            |
1d8
228|hiddenField|__VIEWSTATE|/wEPDwUKMjA3OTY1ODc0Nw9kFgICAw9kFgICAw9kFgJmD2QWBAIBDw8WAh4HVmlzaWJsZWhkFgQCAQ8PFg
IeBFRleHQFBkFuZHJld2RkAgMPDxYCHwEFCmRlbiBIZXJ0b2dkZAIDDw8WAh8AZ2QWAgIBDw8WAh8BBRpXZWxjb21lOiBBbmRyZXcgZGVuIEhl
cnRvZ2RkZD1YbZtEvITGhXSRS3Xr2lM4dxyZ|0|asyncPostBackControlIDs|||0|postBackControlIDs|||10|updatePanelIDs||tup
GreetMe|0|childUpdatePanelIDs|||9|panelsToRefreshIDs||upGreetMe|2|asyncPostBackTimeout||90|9|formAction||test.
aspx|12|pageTitle||Ajax Example|
0

So for the most basic of calls, with NO other controls on the screen at all, you're still looking at a round trip payload of 274 upstream + 668 downstream = 942 bytes. Might not seem like much yet, but once you put more controls on your screen, that sucker's going to get big.

Achieving this in JSON is a bit more involved, but not impossible.

First thing you need to do is to create a webservice, call it WebService.asmx. It should look something like this:

using System;

using System.Web;

using System.Web.Services;

using System.Web.Services.Protocols;

using System.Web.Script.Services;

 

namespace MyAjax

{

    [WebService(Namespace = "http://tempuri.org/")]

    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

    [ScriptService]

    public class WebService : System.Web.Services.WebService

    {

        [WebMethod]

        public string Greet(string firstname, string lastname)

        {

            return string.Format("Welcome: {0} {1}", firstname, lastname);

        }

    }

}

Next thing is we need to overhaul the original web page so that it only uses web service method calls:

<html xmlns="http://www.w3.org/1999/xhtml" >

<head runat="server">

    <title>Ajax Example</title>

<script type="text/javascript">

    function lnkSubmit_Click()

    {

        var firstname = document.getElementById("txtFirstname").value;

        var lastname = document.getElementById("txtLastname").value;

        MyAjax.WebService.Greet(

            firstname,

            lastname,

            function(returnVal)

            {

                var inputPanel = document.getElementById("pnlInput");

                var outputPanel = document.getElementById("pnlOutput");

 

                inputPanel.style.display = 'none';

                outputPanel.style.display = 'block';

 

                document.getElementById("pnlOutput").innerHTML = returnVal;

            });

    }

</script>

</head>

<body>

    <form id="frmMain" runat="server">

    <asp:ScriptManager runat="server" >

        <Services>

            <asp:ServiceReference Path="WebService.asmx" />

        </Services>

    </asp:ScriptManager>

    <div>

        <div id="pnlInput">

            Firstname: <input type="text" id="txtFirstname" /><br />

            Lastname: <input type="text" id="txtLastname" /><br />

            <a href="javascript:lnkSubmit_Click()" id="lnkSubmit">Submit</a>

        </div>

        <div id="pnlOutput" style="display:none;"></div>

    </div>

    </form>

</body>

</html>

To explain the code, we've blown away the update panels, converted the regular panels to divs, and got rid of all server controls.

The script manager on the page has a ServiceReference to WebService.asmx, which creates the javascript function MyAjax.WebService.Greet() which makes calls to the Web Service very simple. For this to work, you need to ensure that your web service contains the attribute [ScriptService]

The hiding and showing of divs is simple javascript/css manipulation.

You'll notice when I call the Web Service method Greet(string, string) I'm passing three parameters. The third parameter is a delegate which gets executed on a successful round trip, with the return value held in returnVal.

This seems like a lot of work, but lets see what the benefits are in terms of our network usage:

Request:

{"firstname":"Andrew","lastname":"den Hertog"}

Response:

"Welcome: Andrew den Hertog"

That's a roundtrip of upstream 46 + downstream 26 = 72bytes in total. That's just 7% of the overhead from the original model. Furthermore if you added more server controls to the page, this method will always just return 72bytes, whereas the ASP.NET AJAX implementation will grow and bloat to accomodate the viewstate.

To reiterate, ASP.NET AJAX is very easy to work with, but it does add significant overhead to your system. Using JSON and Web Services where possible will speed up your site, but perhaps at the loss of code maintainability and site complexity. It's your call to see where you should and shouldn't change your approach, but the more you can do to reduce your network usage, the fast your site will appear and the happier the end user will be.

«November»
SunMonTueWedThuFriSat
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678