Friday, August 6, 2010

C# and Ajax

WTF is Ajax?

Ajax is actually an acronym for Asyncronous Javascript and XML, but in recent times Ajax has become the defacto name for a method to transfer data seemlessly between a client and server (potentially multiple servers) without effecting the user experience.

Originally it was designed as a collection of technologies used in a manner which made client/server communication smoother from the users perspective. It was a solution to the constant 'post back' effect which plagued all systems prior to Ajax's intervention.

Based on the page life cycle and the stateless nature of HTTP, every interaction with a web server required that all session information either be carried with the client or persisted on the server (typically in the form of a session).

Page Life Cycle

In order to understand Ajax and Postbacks, you need to understand why it happens in the first place. With a fat client, the application is loaded into memory and is able to be connected to its resources until the time the user closes the application (or windows shits itself and closes it for you). If you wanted to use the application again you simply loaded it back into memory and the application regained its access to its resources once again.

A web page is different because of its disconnected nature. A web page simply is a content returned from a web server, rendered within a web browser. This is the typical page life cycle, please note however there are more steps to it, this is just the basic overview necessary to understand the cycle.

  1. Client makes a request for a page in a browser (or other User Agent)
  2. User Agent (typically the browser) will resolve the URL to an IP address using DNS
  3. UA will attempt a connection to the resolved IP on port 80 (unless the URL is specified with another port in the format of http://somedomain.com:1234 <- 1234 is the port that the UA will attempt a connection on)
  4. If the connection is successful the UA typically issues a series of HTTP commands. Most likely the HTTP GET command followed by the requested path is issued to the web server (ie. http://www.someplace.com/this/is/a/path.html, resoves www.someplace.com to an IP and then when the conncetion is made a GET /this/is/a/path.html is issued to the web server)
  5. If the server is able to map the 'requested path' to a resource on the server, it will dump whatever the resource outputs without any change, directly back to the UA
  6. If the server is unable to map the 'requested path' to a resource on the server, it will dump a default set of content and issue an HTTP Error code (typically an HTTP/404 error)
  7. Once the server has finished the dump, the connection is closed and the UA is left to render the content in whatever manner it is able to (typically a CONTENT-TYPE is also passed back with the response so the UA knows how to render the content but that is not the servers responsibility, its the resources responsibility to specify it)
When a user submits data on a form that has just been served to them, the ACTION will determine what resource on the server to call and the page life cycle starts again.

The rendering process of the UA (typically) involves a screen clearing method designed to empty all the elements from the Document Object Model (DOM). The new data is then parsed (if it's HTML) into a DOM Tree and rendered accordingly.

Javascript and its role in the process

When a page is rendered to the browser, a Javascript Object Model is made available to execute anything contained within the <SCRIPT> tags in the content.

The script itself can be passed with the content to the UA or the content itself can 'load' external javascripts by use of the <SCRIPT SRC=''> tag. This will 'post-load' javascript files and then execute them when they have been loaded.

The Javascript Object Model is then responsible for creating certain objects (actually its the browser, but that's a bit beyond the scope of this tute, just pretend its JS's responsibility). One such object is the XMLHttpRequest (xhr) Object. Pre 3rd generation browsers (FF 1, IE 5.5, etc) did not have this object created automatically. Though it can be instantiated if necessary, the 'security' of the browser would sometimes prevent it, so you have to accept that pre 3rd gen browsers will not handle Ajax properly.

The xhr has the ability to open a second connection to anywhere (typically the same server due to the Cross-Domain Scripting restrictions most browsers have in place but it doesn't have to), send and receive data all without the user seeing a thing! Sweet as man!

With this opens a realm of new possible communication methods and as such, we can exploit them to make the user experience that much better. Please note however, the same amounts of time to process of page (ie. just say it takes 5 seconds to load the initial page because of server load) will also apply to Ajax requests. So please don't think that Ajax is going to perform BETTER than postbacks, Ajax takes advantage of the fact that LESS data needs to be transmitted 'over the wire' than a standard page does but the PROCESSING time will always be equivalent to the initial render.

Ajax Performance Comparisons

In order to understand why some people toute Ajax as a performance king, we need to understand where the performance benefits and costs are. Here's a break down on the performance details for both postback and Ajax requests.



Initial Page Load
    100% data
    100% processing

Ajax Request made
    Small amount of data sent to make the request
    100% processing
    Potentially small amount of data sent to fulfil the request

Postback request
    Small amount of data sent to make the requiest
    100% data
    100% processing

As you can see above, postbacks require that 100% of the data (ie. the HTML in the page, the DATA for the form fields, EVERYTHING) is sent to the client during the initial load. This also uses 100% of the processing time (that's not CPU time, that's the amount of time used by the server to server the request) to build the page.

Ajax requests typically use small amounts of data to make the request, utilise all the processing time and TYPICALLY send back a small set of data that is needed to fulfil the request.

The postback will typically send the same small amount of data to make the initial request but then take all the time and data required during the initial page load to fulfil the request.

So from the processing time perspective there is no difference between postbacks and Ajax request. From the data perspective there is potentially a MASSIVE difference, and I'll explain why in the next section.

Ajax's defacto response format

XML is a document format that allows for metadata driven documentation. With XML you are able to send through your data along with metadata to assist with its processing. Processing of data is very important as you typically don't want to just dump data to the client (I say typically as sometimes there is no need to process that data and you really do just want to dump it to the client!).

Most 4th gen browsers support the XML document object in its entirety and provides an excellent XPath engine to allow quick and easy object lookups, but for me personally, I would never send XML over the wire for the following reasons:

  1. XML requires structure and this structure costs bytes!
  2. XML does have excellent support for parsing client side but its still cumbersome
Although Ajax really does stand to include XML, there is another data format that serves the same purpose, but its smaller, sexier and oh, did I say sexier?

Ajax's step child, JSON
JSON really isn't a format in as much it is the default way that Javascript has rendered its own object notation for like, well forever actually!

JSON stands for JavaScript Object Notation and truly is a data format that is native to Javascript. Let's check out a comparison between XML and JSON for representing the exact same data structure.

XML
<?xml version="1.0" encoding="utf-8" ?>
<root>
    <child id="child1" isLeaf="false">
        <grandChild id="gc1" isLeaf="true" />
        <grandChild id="gc2" isLeaf="true" />
    </child>
</root>

Grand total: 191 bytes

JSON
[{
    id: 'child1'
    , isLeaf: false
    , children: [{
        id: 'gc1'
        , isLeaf: true
    }, {
        id: 'gc2'
        , isLeaf: true
    }]
}];

Grand total: 136 bytes

This is a simple example but already there is a 28% saving on data that is being transmitted, plus, the JSON data is ALREADY in a format ready to be used by Javascript!

JSON is fast becoming the defacto data format for Ajax, and with almost every Ajax library out there supporting it fully, the distribution and acceptance of JSON is world wide, therefore Ajax might soon be renamed to the less catchy Ajaj :P

Scenario
Ok so let's see this in action. This scenario is an ecommerce application that has an order form that calculates the total cost of something based upon an 'secret' markup value based on the product code. Here's the details.

Retailer A wants to sell two products to the public. Product 1 has a 10% markup on it and Product 2 has a 25% markup on it. The retailer does NOT want the public to be able to know how much the markup OR the price is and so needs a way to conceal the values. Since Javascript is sent to the client to render, including the markup in the Javascript would fail as the user would only need to View Source to see the individual markups and prices.

Retailer A wants the users to simply select the product and enter a quantity and the server will return the sub total amount for the user.

Details
We're going to provide two inputs, a Product Code and Quantity Input plus a 'Calculate Total' button. Once pressed we are going to send the product code and quantity to the server via Ajax, let it retrieve the product from a database, get the price, markup and quantity, do the math and return it to the client so render the 'Product Total' value on the screen for the client.

We have a few files to look at so firstly, I'll give you the lay of the land.

Site Map
App_Code
    PriceCalculationService.cs
App_Data
Default.aspx
jquery-1.4.2.min.js
main.js
PriceCalculationService.asmx
web.config

PriceCalculationService
This is a standard C# web service file. In an ASP.Net project, the App_Code folder contains all the C# code files as source code and prevents their download by the webserver. Therefore this web service is split into two files, the ASMX and the CS file. The ASMX file is the entry point (ie. the thing you actually reference with your Ajax request) and the CS file contains the actual business logic. We want the public to see the ASMX but don't want them to see the CS file so anything in the App_Code folder automatically fails when its is requested. In fact, we only need it during the build which happens outside of the webservers domain so it's all good.

JQuery
Currently using 1.4.2.min in this project but the $.ajax() method should be available in all future versions (don't hold me to that :P)

web.config
Standard file, just ignore it in this tute.

main.js
This is the primary client side business logic file. I tend to separate out the Javascript from the ASPX files for various good reasons :)

Starting the Process
Ok so we've requested the ASPX file and it's rendered a BEAUTIFULLY boring HTML form with two input fields and a button. Sweet! Let's check out Default.aspx

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>AJAX Tutorial</title>
    <script type="text/javascript" src="jquery-1.4.2.min.js"></script>
    <script type="text/javascript" src="json2.js"></script>
    <script type="text/javascript" src="main.js"></script>
</head>
<body>
    <h1>Product Order Form</h1>
    <form>
        <table border="0">
            <tr>
                <td>
                    Product Code (1 or 2):
                </td>
                <td>
                    <input type="text" id="pcode" />
                </td>
            </tr>
            <tr>
                <td>
                    Qty:
                </td>
                <td>
                    <input type="text" id="qty" />
                </td>
            </tr>
            <tr>
                <td>
                    Product Total:
                </td>
                <td>
                    <span id="productTotal">$0.00</span>
                </td>
            </tr>
            <tr>
                <td colspan="2" style="text-align: right;">
                    <input type="button" value="Calculate Total" onclick="calcTotal();" />
                </td>
            </tr>
        </table>
    </form>
</body>
</html>

Here we have pcode and qty as the inputs and productTotal as the SPAN that will hold our output. Clicking on the button will fire the calcTotal() Javascript function.

Pretty stock standard HTML here. Let's check out the Javascript!

// main.js - Created by Bradley J. Gibby q:)
// Version 1.0 - 2010-08-06

function calcTotal() {
    var pcode = $('#pcode');
    var qty = $('#qty');

    $.ajax({
        type: 'POST',
        url: 'PriceCalculationService.asmx/CalculatePrice',
        data: '{"productCode":"' + pcode.val() + '","qty":"' + qty.val() + '"}',
        contentType: 'application/json; charset=utf-8',
        dataType: 'json',
        success: function(msg) {
            var results = eval('(' + msg.d + ')');
            $('#productTotal').html('$' + results.total);
        }
    });
}

Ok simple one function Javascript file...So what's it doing?

    var pcode = $('#pcode');
    var qty = $('#qty');

I personally HATE it when devs do this but for brevity I've put it here. Why do I hate it? Well look at it this way. This is what happens in memory when you do it this way.

  1. jQuery traverses the entire DOM to do the lookup for the selector #pcode
  2. A jQuery object is created, memory allocated
  3. The jQuery object is populated with the hundreds of properties associated with the INPUT tag #pcode
  4. The jQuery object is returned
  5. A memory allocation for the newly created jQuery object occurs
  6. The jQuery object is stored within the memory location (variable) called pcode
Versus doing it this way in version 1.1 (don't use 1.1, it's here JUST as an example for this point)

// main.js - Created by Bradley J. Gibby q:)
// Version 1.1 - 2010-08-06 - Revised for optimsation

function calcTotal() {
    $.ajax({
        type: 'POST',
        url: 'PriceCalculationService.asmx/CalculatePrice',
        data: '{"productCode":"' + $('#pcode').val() + '","qty":"' + $('#qty').val() + '"}',
        contentType: 'application/json; charset=utf-8',
        dataType: 'json',
        success: function(msg) {
            var results = eval('(' + msg.d + ')');
            $('#productTotal').html('$' + results.total);
        }
    });
}

See here...

        data: '{"productCode":"' + $('#pcode').val() + '","qty":"' + $('#qty').val() + '"}',

The point I'm trying to make is this. If you plan to reuse the object that you get back from your call, store it in a variable instead of making calls to retrieve it OVER and OVER again, BUT, if you NEVER plan to use it more than once, don't allocate the memory to store it, just use it directly like I have above.

Anyhow, I digress... So what's it doing? :P

    var pcode = $('#pcode');
    var qty = $('#qty');

Essentially we're retrieving two jQuery objects for later use.

    $.ajax({

Here is the jQuery Ajax method. This method takes a 'JSON' object, actually it takes a Javascript object written in JavaScript Object Notation (ie. JSON). Let's look at the JSON object.

    {
        type: 'POST',
        url: 'PriceCalculationService.asmx/CalculatePrice',
        data: '{"productCode":"' + pcode.val() + '","qty":"' + qty.val() + '"}',
        contentType: 'application/json; charset=utf-8',
        dataType: 'json',
        success: function(msg) {
            var results = eval('(' + msg.d + ')');
            $('#productTotal').html('$' + results.total);
        }
    }
   
Type is set to 'POST' (trust me, for ASP.Net/C#/Webservices it NEEDS to be set to POST - Another LOOOOOOOOOOOOOOONG story for that one). Url is set to our entry point, in this case it's 'PriceCalculationService.asmx/CalculatePrice'. Notice that the URL itself isn't just 'PriceCalculationService.asmx', that's because the web service is expecting to be told what METHOD to call (and trust me, you're in for another LONG story as to why it's been done this way by Microsoft!).

Ok here's the kicker. The quirk with using something other than XML as output is the data and type fields. Since type must be set to POST, even when you're doing a GET request, you also need to send something along with the data field or else you'll potentially get a 'Length Required' error. This is because the POST HTTP command requires a Content-Length to be sent along with it. When making a GET request it's not necessary. To get around this, if we are making a GET request, simply pass '{}' to the data parameter.

This will force jQuery to set a Content-Length value and get past the Length Required error.

DataType should be set to 'json' as that's what you're expecting back.

ContentType is one of the gotchas as well. 'application/json; charset=utf-8' is the required ContentType and helps the process along by telling the WebSerivce what to expect your data to be in.

So let's look at how you pass data through to the web service.

        data: '{"productCode":"' + pcode.val() + '","qty":"' + qty.val() + '"}',

As you can see here, the data itself is a STRINGified version of an actual JSON object. We'll go into more detail about this shortly.

        success: function(msg) {
            var results = eval('(' + msg.d + ')');
            $('#productTotal').html('$' + results.total);
        }

And lastly the success function. The parameter 'msg' is a special ASP.Net JSON object that get's sent back with one single property, the property 'd'. Don't ask why, I can't seem to find out the answer, but it looks like that's how ASP.Net actually handles the JSON version of its output internally. In order to access we EVAL the 'msg.d' property and the results variable now becomes a JSON object ready to be 'probed'.

In this example the webservice is sending back a JSON object with a single property called 'total'. We then set the productTotal SPAN's innerHTML to the '$' + results.total and thus update the client without a postback! Mission accomplished.

The Web Service
Here's the back end web service code that performs the business logic and outputs the JSON object to the client server.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.Web.Script.Services.ScriptService]
public class PriceCalculationService : System.Web.Services.WebService {
    public PriceCalculationService() {
        //InitializeComponent();
    }
   
    [System.Web.Script.Services.ScriptMethod]
    [WebMethod]
    public string CalculatePrice(int productCode, int qty) {
        double markup = 0;
        double price = 0;
       
        switch(productCode) {
            case 1:
                markup = 1.1;
                price = 15 * qty;
                break;
           
            case 2:
                markup = 1.25;
                price = 25 * qty;
                break;
               
            default:
                markup = 0;
                price = 0;
                break;
        }
       
        return "{'total':'" + (price * markup) + "'}";
    }
}

There are two things of note on this before going any further. Firstly the webservice itself MUST have the Script Service attribute (and Script Method attribute) applied to it so that the output can be done correctly.

The CalculatePrice method can be figured out for yourself. Typically you'd have this connect to whatever data source you needed to do the product lookup, then retrieve the appropriate prices and markups, do the math and output the results.

Let's look at the following line:

        return "{'total':'" + (price * markup) + "'}";

At the moment we are outputting a string, in the JSON format, quoted correctly but in essence it's just a string (which matches the public string CalculatePrice signature).

You CAN use a JSONObjectSerialiser if you wish but in this example we're only interested in simple.

The JSON object contains just one property called total with the appropriately calculated total.

Conclusion
Well taking it right back to the beginning, the basic flow works out as:

Page Load -> User does something requesting a partial page update -> Javascript prepares an Ajax request and sends it -> Server receives request, processes it and prepares a JSON response then sends it -> Javascript receives the response, converts it to a real JSON object and uses the object to change something on the client WITHOUT the need to update the entire bloody page!

No comments:

Post a Comment