Archive for category Code

Check Existing Values with ASP.NET CustomValidator + jQuery $.ajax()

‘SjAJ’ ASP.NET CustomValidator

SjAJ?! Sorry, I couldn’t resist. I had started with AJAX, but thinking now… hmmm, the method being described here is Synchronous jQuery And JSON.

Something tells me that SjAJ is not going to make it to buzzword status. Oh well, I tried.

We will try to quickly outline a method for using the ASP.NET CustomValidator control in conjunction with jQuery and the AJAX Control Toolkit.

Initially, I had tried to use other APIs to call my web services and/or page methods. Problem was, at least that I could tell, was that the asynchronous nature of these operations cause an issue inside the ClientValidationFunction specified in the CustomValidator control.  Eventually, I revisted the code in my earlier posts, and once again, jQuery came to the rescue! Details follow.

ASP.NET CustomValidator Control

For those not familiar with the CustomValidator control, let me give a brief explanation.

<asp:CustomValidator runat="server" ID="UserNameExistsValidator"
    OnServerValidate="UserNameExistsValidator_ServerValidate"
    ControlToValidate="UserNameTextBox"
    ClientValidationFunction="doesUserExist" />

The bit we’re most concerned with here is the ClientValidationFunction. This will fire during client-side page validation, receiving two arguments.

function doesUserExist(source, arguments) { }

source contains the <span> element rendered for the validation control. Since we are going to use the ASP.NET Ajax Control Toolkit’s ValidatorCalloutExtender we will ignore the argument source.

arguments is much more interesting here, as it contains two properties: IsValid and Value

As you might expect, Value contains the value contained in the ControlToValidate that we’ve specified in our validator control. IsValid is what we’ll use to indicate whether or not the value we’re checking exists or not.

Here’s a simple, albeit very contrived, example of how the ClientValidationFunction works:

<asp:CustomValidator runat="server" ID="ZipCodeCustomValidator"
    ControlToValidate="ZipCodeTextBox"
    ClientValidationFunction="isValidZipCode" />

<script type="text/javascript">
    function isValidZipCode(source, arguments) {
        var zipCode = arguments.Value;
        var zipCodeValid = /^\d{5}(-\d{4})$/.test(zipCode);

        arguments.IsValid = zipCodeValid;
    }
</script>

Again, very contrived, and quite silly, actually – we would be much better off using a RegularExpressionValidator for this particular case… but I digress.

Briefly, this CustomValidator uses client-side code to test the value entered in a text box (ZipCodeTextBox in this case) against the regex /^\d{5}(-\d{4})?$/ which will allow values such as 95060 or 95060-1234 but not 950-0000 nor 95064-0.

Checking for An Existing Value

As mentioned earlier, I was having trouble getting things to work using a few asynchronous techniques. It seemed that if arguments.IsValid is set inside a callback function, it doesn’t register properly, and the control always appears valid.

Luckily, jQuery’s $.ajax() function has a handy option: async, which when set to false does what we need – it waits for the response from the server before continuing to our success function.  Without further ado…

function doesUsernameExist(source, arguments) {
    var exists;
    $.ajax({
        type: "POST",
        contentType: "application/json; charset=utf-8",
        url: "Default.aspx/DoesUserExist",
        data: "{'username': '" + arguments.Value + "'}",
        dataType: "json",
        async: false,
        success: function(result) {
            exists = result.d;
        }
    });
    arguments.IsValid = !exists;
}

In line 6, we specify the name of our PageMethod contained inside our Default.aspx page. This is only one of many ways to accomplish this, of course. See http://brian.dobberteen.com/code/responding-to-jquery-ajax-request-with-php/ for my treatment of using PHP as a ‘web service’ of sorts.

Line 9 is the all-important async: false that seems to provide the needed magic to get this all working.

Finally, on line 14, we set our CustomValidator’s IsValid property to true only if the value we are checking for did not exist.

To make things easy, let’s use the ASP.NET membership system to check for an existing username. This has the benefit of providing the method for both server and client side validation. Here goes:

using System;
using System.Web.Security;
using System.Web.Services;
using System.Web.UI;

public partial class _Default : Page
{
    protected void Page_Load(object sender, EventArgs e) { }

    [WebMethod]
    public static bool DoesUserExist(string username)
    {
        return Membership.GetUser(username) != null;
    }

    protected void UserNameExistsValidator_ServerValidate(object source, ServerValidateEventArgs args)
    {
        args.IsValid = !DoesUserExist(args.Value);
    }
}

The logic here is very simple. On line 13, we use the ASP.NET Membership system to check for an existing username. This logic also serves us well for our OnServerValidate event handler, starting on line 16.

The .aspx page:

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="cc1" %>

<!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>Check Existing Usernames</title>
    <style type="text/css">
        .invalid {
            border: solid 1px #ff0000;
            background-color: #ffcccc;
        }
    </style>
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js">
    </script>
    <script type="text/javascript">
        function doesUsernameExist(source, arguments) {
            var exists;
            $.ajax({
                type: "POST",
                contentType: "application/json; charset=utf-8",
                url: "Default.aspx/DoesUserExist",
                data: "{'username': '" + arguments.Value + "'}",
                dataType: "json",
                async: false,
                success: function(result) {
                    exists = result.d;
                }
            });
            arguments.IsValid = !exists;
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <asp:ScriptManager runat="server" ID="sm1" />
    <div>
        Username:
        <asp:TextBox runat="server" ID="UserNameTextBox" CausesValidation="true" />
        <asp:Button runat="server" ID="ClientSubmitButton" Text="Check Username Client Side"
            OnClientClick="Page_ClientValidate(); return false;" />
        <asp:Button runat="server" ID="ServerSubmitButton" Text="Check Username Server Side"
            OnClientClick="ValidatorEnable(UserNameExistsValidator, false);" />
    </div>
    <asp:CustomValidator runat="server"
        ID="UserNameExistsValidator"
        ControlToValidate="UserNameTextBox"
        ClientValidationFunction="doesUsernameExist"
        OnServerValidate="UserNameExistsValidator_ServerValidate"
        Display="None"
        ErrorMessage="Username Already Exists" />
    <cc1:ValidatorCalloutExtender runat="server"
        ID="UserNameExistsValidator_CalloutExtender"
        TargetControlID="UserNameExistsValidator"
        HighlightCssClass="invalid"
        PopupPosition="BottomLeft" />
    </form>
</body>
</html>

And finally, a screenshot of what the validator looks like when I enter ‘bdobberteen’ for the username – an account I created minutes earlier:

I am not sure if this degrades gracefully in the face of a non-javascript browser. Namely, the ValidatorCalloutExtender won’t display. A possible solution would be to add a ValidationSummary control, initially set its visibility to false, and, upon postback, if the server-side validation fails (!Page.IsValid), set that same ValidationSummary to visible, allowing downstream browsers to view the various error message(s).

Hope this helps someone!

- brian -

Sample Code for Article

Calling a Web Service with ASP.NET AJAX Client Script

Wow! It’s been waaaaay too long since my last installment – been busy with latest programming contract, not that you care.

Since beating the proverbial dead horse is my specialty, this brief post is going to cover making a call to the Yahoo! Maps API via an ASP.NET web service (.asmx) which we then call from client script with the aid of the Microsoft ASP.NET AJAX library(ies).

In order to access the necessary JavaScript script libraries, we must first include the customary ScriptManager object in our page:

<asp:ScriptManager runat="server" ID="sm1">
    <Services>
        <asp:ServiceReference Path="~/ZipCodeService.asmx" />
    </Services>
</asp:ScriptManager>

As you may have already guessed, this declaration creates a ScriptManager for our page and includes a reference to the web service (which we will create momentarily) used for fetching the City and State upon user input of a US Zip Code.

Without further ado, here’s the complete web service code:

using System.Linq;
using System.Web.Script.Services;
using System.Web.Services;
using System.Xml.Linq;

namespace YahooMapsZipCodeLookup
{
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    [System.Web.Script.Services.ScriptService]
    public class ZipCodeService : System.Web.Services.WebService
    {
        private static readonly string MAP_URL = "http://local.yahooapis.com/MapsService/V1/geocode?appid=";
        private static readonly string MAP_APP_ID = "<YOUR ID HERE>";

        [WebMethod]
        public CityState GetCityStateByZip(string zipCode)
        {
            XDocument xdoc = XDocument.Load(MAP_URL + MAP_APP_ID + "&zip=" + zipCode);

            var state = root.Descendants().Where(el => el.Name.LocalName == "City").Single().Value;
            var city = root.Descendants().Where(el => el.Name.LocalName == "State").Single().Value;

            return new CityState { City = city; State = state; };
        }
    }
    public class CityState
    {
        public string City { get; set; }
        public string State { get; set; }
    }
}

After our ScriptManager registers this service, we now have access to proxy(ies) that can be called from the client side. To do so, we can create a wrapper to the service call:

function getCityStateByZip(zip) {
    YahooMapsZipCodeLookup.ZipCodeService.GetCityStateByZip(
                    zip,
                    onZipLookupSuccess,
                    onZipLookupError,
                    "<%= DateTime.Now %>"  // Passing as the context
         );
}

It’s not as simple as just including the above call – we need to provide the methods named in the second and third parameters (the two lines highlighted in green above). Which would look something like:

function getCityStateByZip(zip) {
    YahooMapsZipCodeLookup.ZipCodeService.GetCityStateByZip(
                    zip,
                    onZipLookupSuccess,
                    onZipLookupError,
                    "<%= DateTime.Now %>"  // Passing as the context
         );
}
function onZipLookupSuccess(result, context, methodName) {
    // Note how we can access the properties of the returned CityState object
    // The ScriptManager takes care of all of this for us!
    $get("CityStateResult").innerHTML = result.City + ", " + result.State;
}
function onZipLookupError(error, context, methodName) {
    // The error object's most interesting method is probably get_message(), which
    // will, in this case, return the message of any ASP.NET exceptions that may
    // have occured on the server side webservice.
    $get("CityStateResult").innerHTML = "Error: " + error.get_message();
}

Though we aren’t making any use of the context and methodName parameters being passed to our callback functions, I’ll just point out that the context is whatever was set when calling the webservice – in this case, the date and time were used. The context can be very useful to track asynchronous calls in a stateless environment.

The methodName, cleverly enough, returns the name of the web service method that we’ve called – in this case, methodName contains GetCityStateByZip which, as we all know, is the name of the only method exposed by our YahooMapsZipCodeLookup.ZipCodeService.

There is another option to writing explicit callback methods, and one that I prefer (probably due to my experience with jQuery). If we don’t want to write explicit methods, we can use anonymous functions as parameters in the call to our web service:

function getCityStateByZip(zip) {
    YahooMapsZipCodeLookup.ZipCodeService.GetCityStateByZip(
                zip,
                function(result, context, methodName) {
                    $get("CityStateResult").innerHTML =
                            result.City + ", " + result.State + "<br />"
                          + "context: " + context + "<br />"
                          + "methodName: " + methodName;
                },
                function(error, context, methodName) {
                    $get("CityStateResult").innerHTML =
                        "error: " + error.get_message();
                },
                "<%= DateTime.Now %>"  // Passing as the context
        );
}

The two highlighted lines indicate where we’ve replace the names of functions with anonymous functions. NB: the line numbers in the above snippet are supposed to correspond with the actual aspx page.

To hook everything up on our actual page, we need to add an event handler to, in this case, a textbox:

<input type="text" onchange="getCityStateByZip(this.value);" />
<input type="submit" value="Get City and State" onclick="return false;" />
<div id="CityStateResult"></div>

I also snuck in the CityStateResult div that is the target of our DOM manipulation upon receipt of a response from the web service. I also added a do-nothing submit button to demonstrate that a button press will fire the textbox’s onchange event.

The entire aspx page:

<%@ Page Language="C#" %>
<!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>Calling a Web Service from ASP.NET AJAX Client Script</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <asp:ScriptManager runat="server" ID="sm1">
        <Services>
            <asp:ServiceReference Path="~/ZipCodeService.asmx" />
        </Services>
    </asp:ScriptManager>
    <input type="text" name="ziptxt" id="ziptxt" onchange="getCityState(this.value);" />
    <input type="submit" value="Get City and State" onclick="return false;" />
    <div id="cityStateResult"></div>
    </div>
    <script type="text/javascript">
        function getCityState(zip) {
            var citystate =
                YahooMapsZipCodeLookup.ZipCodeService.GetCityStateByZip(
                    zip,
                    function(result, context, methodName) {
                        $get("cityStateResult").innerHTML =
                            result.City + ", " + result.State + "<br />"
                         + "context: " + context + "<br />"
                         + "method name: " + methodName;
                    },
                    function(error, context, methodName) {
                        $get("cityStateResult").innerHTML =
                            "error: " + error.get_message();
                    },
                    "<%= DateTime.Now %>"  // Passing as the context
                 );
        }
    </script>
    </form>
</body>
</html>

In a nutshell, when the user triggers the onchange event of our textbox, the event is handled by our wrapper function getCityStateByZip(zip), to which we pass this.value, which contains the changed value of the textbox. This value is then passed to the web service, which returns a CityState object – ASP.NET AJAX handles all of the serialization and other such details for us in a nearly transparent way. We can refer to the CityState object returned by our web service as if the object was a normal .NET object, i.e. result.City or result.State.

And I think that covers it! No more posts on zip code lookups, I promise!

Before I forget, here is the source to the samples seen above: Calling a Web Service with ASP.NET AJAX Client Script Source Code

Using LINQ to XML instead of XmlReader

Previously, I mentioned that I wasn’t sure if using an XmlReader was the best way to fetch the City and State values from the XML response from the Yahoo Maps API:

Using reader As XmlReader = XmlReader.Create(MAP_URL & MAP_APP_ID & "&zip=" & zip)
  While reader.Read
    If reader.Name = "City" And reader.IsStartElement Then
      ret.City = reader.ReadElementString("City")
    End If

    If reader.Name = "State" And reader.IsStartElement Then
      ret.State = reader.ReadElementString("State")
    End If
  End While
End Using

We were using the above to basically read the XML response line-by-line, checking for the two elements we are interested in, and storing their values in our simple CityState object ‘ret.’ Again, the XML we are processing looks like:

<?xml version="1.0" ?>
<ResultSet -xmlns omitted for clarity->
  <Result precision="zip">
    <Latitude>36.993591</Latitude>
    <Longitude>-122.060199</Longitude>
    <Address />
    <City>Santa Cruz</City>
    <State>CA</State>
    <Zip>95064</Zip>
    <Country>US</Country>
  </Result>
</ResultSet>
<!--  ws05.ydn.gq1.yahoo.com compressed/chunked Thu Jul  9 14:54:33 PDT 2009 -->

Though our XmlReader method works fine, it seemed a bit clunky to me, so I set out to check out LINQ to XML to see if we can clean the code up a little bit.

LINQ is still somewhat of a new concept for me, as I haven’t had much chance to work with it given the nature of much of the work I do. Its potential usefulness, however, seems pretty clear to me, and definitely look forward to working with it as much as possible – at least until the next latest, greatest thing comes along! I’ve always felt the LINQ syntax to be a bit unusual, and – unlike I’d first hoped – not that close to SQL. Of course, LINQ isn’t SQL, so I suppose I shouldn’t be surprised that LINQ doesn’t look more like:

SELECT element FROM elements WHERE element.name = 'City'

Of course, this is an incredibly trivial example, and the only difference between a similar LINQ query is the arrangement of the three components of the query (SELECT, FROM, and WHERE). But in reading that I’ve done, LINQ can get pretty cryptic at times… though I used to feel the same way about complex SQL, which now is very easy for me to comprehend.

At any rate, we’ll be dropping our use of the XmlReader class and instead opt for a LINQ-to-XML XDocument object. We need to change our Imports declaration slightly, removing System.Xml and replacing it with System.Xml.Linq. Much the same as we did with the XmlReader, we use XDocument’s static function Load to grab our XML from the Yahoo Maps API (more about accessing the Yahoo Maps API).

Dim xdoc As XDocument = XDocument.Load(MAP_URL & MAP_APP_ID & "&zip" = & zip)

Just like our earlier examples, this is contained within a PageMethod that accepts ‘zip’ as its only parameter and returns a simple CityState object. After the XDocument is loaded, we can now perform a LINQ query on it, allowing for us to easily specify what elements (City and State, in this case) we’d like the value of.

Dim city = (From el in xdoc.Descendants _
              Where el.Name.LocalName = "City" _
              Select el.Value).Single

Because we’re interested in the City and State elements, which are descendants of the root ‘ResultSet’, we’re using the cleverly named property Descendants of our XDocument object. Descendants is an IEnumerable collection of XElement objects which is what makes our LINQ operations possible. As you can see, the LINQ query first specifies where to look for something (akin to the FROM clause in an SQL SELECT statement), then we set the criteria to be sure we find the ‘City’ element. I discovered that el.Name won’t work for us, as the Name property of an XElement object is an XName object. The LocalName property of an XElement refers to the elements unqualified name, without any namespace info included. The LINQ statement next ‘Selects’ the element matching our criteria. Finally, since we are only expecting a single city to exist in our XML response, we wrap the whole LINQ query in parentheses and use .Single to return the one-and-only value we’re after. .Single will throw an exception if more than one element was found by our query. To get the value of the State element, just replace Where el.Name.LocalName = “City” to Where el.Name.LocalName = “State”.

I’ve experimented with polling both the City and State elements in one pass, i.e.

Where el.Name.LocalName = "City" Or el.Name.LocalName = "State"

But this just seemse to necessitate another query to be sure that we’re getting the right values from our new, albeit smaller, collection of XElements. So, for now, we’ll just perform two seperate LINQ operations… please let me know if there are better ways of going about this (and I’m sure that there are!)

Finally, let’s take a quick look at using a Lambda expression to streamline this whole thing a bit more. The following gives us the same result as the first LINQ query above:

Dim city = xdoc.Descendants.Where(Function(el) el.Name.LocalName = "City").Single.Value

The call to .Where from our xdoc.Descendants is a call to an ‘extension method.’ These extension methods are central to the functionality of LINQ. In this case, we’re passing an ‘anonymous function’ to the Where extension method that we will be using to apply our criteria. The anonymous function is passed el which is an XElement object. And again, we want an element with a LocalName of City, so the body of our anonymous function does just this, compares the LocalName to ‘City’, and if it matches, the expression continues by fetching the Single city element’s Value.

Clearly, we’ve only just scratched the surface of LINQ and LINQ to XML, but we gotta start somewhere, no?

Responding to jQuery AJAX Request with PHP

I will start by saying that my experience with PHP is somewhat limited, so this is more of an exercise for my benefit. I’d like to take ASP.NET out of the formula for a bit here, and instead resort to a simple PHP page to answer AJAX requests from jQuery.

Earlier, we used ASP.NET PageMethods, but let’s see a possible PHP solution.

Again, we’ll just be performing the trivial task of retrieving a city/state location based on a zip code entered on our form.

...
<form id="form1">
  <input type="text" name="ZipCodeTextBox" id="ZipCodeTextBox" />
</form>
...

Since we’ll use jQuery to bind this textbox to the onchange event, there are no additional attributes needed in our markup here.

<script type="text/javascript">
$(function() {
});
</script>

A quick note on jQuery for those new to it: in line 2 above, the rather terse statement $(function() is shorthand for “wait until the page’s document object model (DOM) is loaded so that we can bind event handlers, set CSS properties, etc. to page elements.” The DOM loads before the images on a page are done downloading, so this allows for our jQuery to work as soon as it is able to. $ is essentially shorthand for jQuery itself and is central to its operation. Wrapping an element in $() bestows upon that element with all the power of jQuery, allowing for extensive manipulation through chaining of functions.

jQuery also allows for us to create new DOM elements by using the syntax:

$("<div id='div1'></div>")

In this example we have created a div with an id attribute of ‘1′. Even though this element now “exists,” it is not actually in the DOM. Rather than include an <input> in our XHTML, let’s instead create it with jQuery. With chaining, we can handle not only the creation, but the binding of the onchange event, some simple CSS styling, and finally, the insertion of the new element into the page.

Because ASP.NET is pretty specific about a PageMethod can respond to a client-side reqest, we used the $.ajax() function in our earlier example, as it allows for the most granular approach to sending an AJAX request. But now, since we are going to build a simple PHP script to respond to a GET request, there is a very handy jQuery function, namely $.getJSON():

$(function() {
  $("<input type='text' id='ZipCodeTextBox' name='ZipCodeTextBox' />")
    .bind('change', function(event) {
      if (/^\d{5}(-\d{4})?$/.test($(this).valI())) {
        $.getJSON("ZipCodeService.php", {zip: $(this).val()}, function(msg) {
          $("<label />").text(msg.City + ", " + msg.State).appendTo("#form1");
        });
      }
    })
    .appendTo("#form1");
});

Lines 2 and 6 demonstrate jQuery’s ability to generate new DOM elements on the fly – in this case a new textbox and a new label – and perform various operations on them, including inserting them into the page. Here we use appendTo() to place our new elements after the last child element of what we’ve selected, in this case #form1.

Line 5 is the call to $.getJSON, whose signature looks like:

$.getJSON(url, parameters, callback)

In our example, we use ZipCodeService.php as our URL, which refers to the script we’re about to create that will reside in the same location as our XHTML file. For parameters, I chose to create a simple object using JSON notation, {zip: $(this).val()}, which will be turned into proper query string parameters by jQuery. We could also have set our parameters as they would appear in a query string, i.e. “zip=” + $(this).val() – in this simple GET request, this is a pretty trivial point, but if you were working with a more complicated javascript object, it would be a lot easier to simply pass it as a whole rather than trying to manually serialize it. Finally, the callback parameter to $.getJSON() is the name of a function that will fire following completion of the AJAX request – it features two parameters, the first containing the javascript object returned and the second the status of the request. We are going to ignore the status parameter, so our anonymous callback function looks like:

function(msg) { $("<label />").text(msg.City + ", " + msg.State).appendTo("#form1"); }

Doesn’t get much easier! All we had to do was return a JSON encoded object from our PHP function and in two lines of javascript, we have direct access to the City and State properties that we requested.

Now, let’s check out ZipCodeService.php – please note: this is my first foray into the world of PHP and XML, so if I’ve badly butchered anything, please let me know!

<?php
// Check to make sure 'zip' was sent in the GET request
if (!empty($_GET["zip"])) {

  // Set a couple of constants
  define("MAP_URL", "http://local.yahooapis.com/MapsService/V1/geocode?appid=");
  define("MAP_APP_ID", "<YOUR APP ID>");

  // Retrieve the query string parameter 'zip' and store it in $zip
  $zip = $_GET["zip"];

  // Run *another* Regular Expression test on the input
  if (preg_match("/^\d{5}(-\d{4})?$/", $zip)) {

    // Use simplexml_load_file to grab our XML response from Yahoo
    // This works much the same as when we loaded the XML in our ASP.NET
    $xml = @simplexml_load_file(MAP_URL . MAP_APP_ID . "&zip=" . $zip);

    // Make sure that our request actually succeeded!
    if ($xml)

      // Because the Yahoo Maps API will give some funky responses to some
      // strings that *look* like US zip codes but are somehow classified as foreign
      // we are going to check that the zipcode is indeed a US postal code and that
      // the Zip element is set in the response - something which is absent on the
      // postal codes deemed to be located outside the US by Yahoo Maps
      if (!empty($xml->Result->Zip) && $xml->Result->Country == "US")
        $ret = array('City' => (string)$xml->Result->City, 'State' => (string)$xml->Result->State);
      else
        return false;
    else
      return false;

  echo json_encode($ret);
}
	}
?>

On line 28, we are using array() to construct a new associative array (aka hash) with the keys ‘City’ and ‘State’ (big surprise, right?!) and their respective values. I found that our simplexml object exhibits some interesting behavior when it comes to casting. It seems that it is smart enough to know to cast to a string automatically when used in some scenarios, such as in a comparison:

if ($xml->Result->City == "Anytown")

But if we try and assign $xml->Result->City to a key in our associative array, it instead assigns the entire simplexml object rather than its string contents. So, in our call to array() we need to cast the two elements as such:

$ret = array('City' => (string)$xml->Result->City, 'State' => (string)$xml->Result->State);

To cast our objects, we simply prepend them with the type to cast to enclosed in parentheses – a lot less verbose than VB’s clunky CType() function!

Now, all we need to do for our client-side $.getJSON() call to be happy is to write back a string representing a JSON object, which is where the handy json_encode() function comes into play. We just pass it our hash $ret and voila! Out pops a JSON string that we simply echo to our client, and that’s it on the server side!

All that’s left now is for the callback function we specified in our call to $.getJSON() to fire, in which a new <label /> is created, has its text set to City, State and is finally appended to our form.

Was this easier that doing it with ASP.NET? Was it lighter-weight? Faster? For something as trivial as this, I think that performance is not much of a consideration at all. Especially since the entire thing bottlenecks on our request to Yahoo Maps. In terms of ease of coding, I think I slightly prefer the ASP.NET PageMethod, though that is probably because I am such a Visual Studio fanboy. The .NET solution still requires two files as does the jQuery/PHP that we’ve concocted here. The XML handling routines in PHP seem just as capable as those in .NET. One thing I can say I like more about the PHP solution is that I feel much more in control of exactly what is going on in terms of generated code (there is none for our PHP solution!). I am not sure how much weight is added to a page when ASP.NET AJAX builds proxies for our client code to talk to a PageMethod, and I do know that jQuery, in minified and gzipped form, only takes 19K – which is awfully small… how close does ASP.NET AJAX come to that number?

I think we’ve pretty much exhausted this whole zipcode lookup thing, so next time I want to examine using nested ASP.NET data controls such as the Repeater, FormView, etc.

Calling a PageMethod with jQuery

This is a quick follow up to the previous post about calling ASP.NET PageMethods in which we used a ScriptManager object with EnablePageMethods set to True.

This time, we won’t even be including a ScriptManager in our page, and instead will use jQuery. Without further ado, here’s what the javascript looks like:

$(function() {
  $("#ZipCodeTextBox").bind('change', function(event) {
    // If it doesn't look like a zip code, don't even bother with the request
    if (/^\d{5}(-\d{4})?$/.test($(this).val()))
      $.ajax({
        type: "POST",
        contentType: "application/json; charset=utf-8",
        url: "Default.aspx/GetCityStateByZip",
        data: "{'zip': '" + $(this).val() + "'}",
        dataType: "json",
        success: function(msg) {
          $("#CityStateLabel").text(msg.d.City + ", " + msg.d.State);
        }
      });
  });
});

If you’re familiar with jQuery, then this should look pretty familiar. If not, you might be surprised how much this small snippet is accomplishing.

First, there was no need to add an onchange attribute to our asp:TextBox like we did in the previous post. The practice of keeping our HTML markup clear from javascript calls for event handlers and such is known as ‘unobtrusive javascript.’ Personally, I think it’s kinda silly, but will admit that the HTML does look cleaner without being cluttered with extraneous attributes and bits of javascript code.  The reason we don’t have to add the onchange attribute is because we are using jQuery to bind our text box to an onchange event handler:

$(function() {
  $("#ZipCodeTextBox").bind('change', function(event) {

First, jQuery waits until the DOM (the page’s logical structure) is loaded so that we can be sure to find our text box we want to bind to. In this case, the ID attribute of the text box is ZipCodeTextBox. Using jQuery’s super-easy element selecting capability, we pass the CSS selector #ZipCodeTextBox to the $() function ($() is shorthand for a call to jQuery). We then call bind() on the text box, passing ‘change’ as the event type and as a second parameter, the function we want to execute in response to the firing of the change event. The function takes a parameter, event in this example, but we won’t be using it here.

Once the onchange event fires for our text box, the block of code inside the function will execute. First, we are going to doing a simple inspection of the value of the text box using a little regular expression. To actually retrieve the value of the text box, we can use $(this), which essentially enhances our text box element, giving it access to all pertinent jQuery features. In this case, $(this).val() is all we need – val() returns the value contained within form elements.

Our regular expression

/^\d{5}(-\d{4})?$/

is used to test the value to make sure that it consists of 5 digits – \d{5} – followed by an optional hyphen and four more digits – (-\d{4})?. Obvious examples of valid zip codes would be 01721-8582 or 95060. If the value entered in the text box doesn’t fit this format, we won’t even bother making a call to our PageMethod.

Putting it all together, we end up with:

/^\d{5}(-\d{4})?$/.test($(this).val())

From here, we call $.ajax(), another jQuery function, which makes an AJAX request using the parameters we specify. ASP.NET requires our request to be POSTed to the server (GET won’t work) as well as have its content type set to ‘application/json.’ The url parameter in our AJAX call is simply the name of the page cotaining the PageMethod (Default.aspx in our sample) followed by a forward slash and the name of the WebMethod in our page’s code-behind file. Our method must be decorated with the WebMethod() attribute in order to respond correctly to the request:

Imports System.Web.Services
...
<WebMethod()> _
Public Shared Function GetCityStateByZip(zip As String) As CityState
...
End Function

The data that we POST to the page needs to be in JSON format, which boils down to a set of name-value pairs contained within curly braces. Since our PageMethod is expecting ‘zip’ as a parameter, we construct our JSON data to look like {‘zip’: ‘95064′}. We then set the data type of the request to ‘json’. The AJAX parameter list to this point:

        type: "POST",
        contentType: "application/json; charset=utf-8",
        url: "Default.aspx/GetCityStateByZip",
        data: "{'zip': '" + $(this).val() + "'}",
        dataType: "json"

Finally, we define the ’success’ parameter to our AJAX call, setting its value to a function whose parameter is the result of our request.

        success: function(msg) {
          $("#CityStateLabel").text(msg.d.City + ", " + msg.d.State);
        }

We can do whatever manipulation of the data we need here, though some would argue that data processing should take place on the server-side. I guess it’s really dependent on the task at hand. At any rate, we are going to do a bit of DOM manipulation here, involving the simple assignment of a pre-defined asp:Label element’s text value to the City and State returned to us from the PageMethod. Again, $() takes a CSS selector as a parameter, returning to us the element we want to modify. And we just want to change the text of the label, so we use the text() function, passing to it the value we’d like set.

Initially, I expected that using msg.City and msg.State would work in the same manner as it did in our Microsoft AJAX callll to the page method. Was rather confusing until I was able to inspect the JSON returned from the PageMethod by using FireBug for Firefox (which if you don’t already have, you need to get right away http://getfirebug.com/). Seems ASP.NET wraps the entire JSON response and assigns it to a key simply named ‘d’. Our JSON response object:

{'d': {'City': 'Santa Cruz', 'State': 'CA'}}

As our JSON object is the parameter msg passed to the success function, to get the City and State values we must include the d in our expression – msg.City will not work, msg.d.City will.

So that’s about it! We managed to call our PageMethod with jQuery rather than the Microsoft AJAX library. I had been thinking that this might even be a suitable application for being moved entirely to the client-side. But then I thought that including an Yahoo Maps Application ID for anyone to view might not be the greatest idea. I think next time I will try and make a simple PHP “web service” to respond to our jQuery AJAX request, taking ASP.NET entirely out of the picture.

UPDATE: Per some requests, I’ve authored a simple demonstration, the source code of which can be found at: Source Code for Calling a PageMethod with jQuery