Archive for category Code

Calling a PageMethod with jQuery

This was selected as an ASP.NET Article of the Day for September 29, 2009

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

Using a PageMethod to Look Up City and State based on Zip Code

This one seems like a pretty good one to try and start with – the simple lookup of a city and state based on a zip code entered on a form. We’ll be using Yahoo’s map API here, for which you’ll need to grab an Application ID (freely available at http://developer.yahoo.com/maps/simple).

Be sure to save that in a suitable location, as we’ll need it to form our request to the API later via a simple HTTP GET request to Yahoo’s server. This GET request is about as simple as they come, only needing the Application ID and the zip code in question as query string parameters. In fact, you can build this request and paste it into your browser’s address bar to verify that you have it right. It looks like:

http://local.yahooapis.com/MapsService/V1/geocode?appid=<YOUR APP ID>&zip=<ZIP CODE>

If you try navigating to the above URL, replacing <YOUR APP ID> with the Application ID you received from Yahoo and <ZIP CODE> with the zip you’d like to check out, you should receive an XML response that looks similar to this:

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

So, getting the info is simple enough, and there are countless ways to handle this, to be sure. In fact, using something like jQuery or even the Microsoft AJAX Library would be very simple and not necessitate the need for PageMethods and the like. So maybe this is a somewhat contrived use of PageMethods, but let’s do it anyway!

I must add, after giving it some more thought, it seems that moving this entire operation to the client would expose our Yahoo Maps Application ID, which is probably not an ideal situation. jQuery would certainly have no problem accomplishing our task, easily handling the parsing of the XML response and so on.

At any rate…

Go ahead and create a new ASP.NET web page and name it whatever you’d like. I’ll be using Default.aspx here, but it really isn’t important. Following the creation of the new page, we’ll be presented with the familiar blank ASP.NET page, replete with a form tag and one lonely div. I’m sure you’ve all seen it a million times, but this is what we’ll be starting with:

<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="Default" %>

<!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></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    </div>
    </form>
</body>
</html>

As you can see, we’ll be using the code-behind model rather than including the script in the .aspx file seen above. In order to access the PageMethod that we will soon be creating, we need to add a ScriptManager object to the page in order to generate the javascript proxy to our server-side method. This couldn’t be much easier, simply add:

<div>
  <asp:ScriptManager runat="server" ID="ScriptManager1" EnablePageMethods="true" />
</div>

Obviously, the EnablePageMethods attribute is key here, and the only change needed to the default ScriptManager.

Now, let’s add a quick textbox to the form:

<asp:TextBox
  runat="server"
  ID="TextBox1"
  onchange="getCityState(this.value);" />

And right underneath the asp:TextBox, let’s add an asp:Label element:

<asp:Label runat="server" id="CityStateLabel" />

Note that we added the attribute ‘onchange’ to the TextBox – it’s set to a function that fires any time the contents of the TextBox change. The function that responds to the event:

function getCityState(zip) {
  PageMethods.GetCityStateFromZip(zip, onGetCityStateComplete);
}

Not a whole lot to that, clearly. However, thanks to the ScriptManager we placed on the page, the plumbing between our server-side PageMethod and our client-side javascript has been taken care of. And in this case, we are only passing one parameter to our PageMethod, whose signature is seen here:

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

Note the Imports System.Web.Services – this is the namespace where the WebMethod() attribute lives. As you can see, this method, which is in our code behind file (Default.aspx.vb) takes the string parameter ‘zip’ and returns a simple CityState object:

Public Class CityState
  Public City As String
  Public State As String
End Class

This little class is defined on the same page as our code behind file and will automatically be serialized in order for our client-side code to consume it.

The second parameter in PageMethods.GetCityStateFromZip(zip, onGetCityStateComplete) is the name of a callback function that will execute upon completion of our call to the PageMethod.

function onGetCityStateComplete(result) {
  if (result != null)
    $get("CityStateLabel").innerHTML = result.City + ", " + result.State;
  else
    $get("CityStateLabel").innerHTML = "";
}

The result parameter being passed here is a CityState object, as defined in the PageMethod’s signature. It has been nicely serialized into a javascript object with properties that are easily accessed. We use the Microsoft AJAX $get() function to find the label we created earlier and then set its innerHTML to the name of the City, a comma, and the name of the state (i.e. Santa Cruz, CA).

Lastly, let’s check out what’s going on in our PageMethod in terms of actually fetching the data from the Yahoo Maps API

<WebMethod()> _
Public Shared Function GetCityStateFromZip(ByVal zip As String) As CityState
  Dim ret As CityState = New CityState

  Try
    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

  Catch ex As Exception
  End Try

  Return ret
End Function

Hopefully the code is somewhat self-explanatory, but let’s review it. .NET is kind enough to allow us to create a new XmlReader object by calling the static method and passing it a URL (line 6). Since we already know how to build the proper URL to get our XML response from Yahoo Maps, we just pass this value. We’re getting the zip code from the zip code text box following its onchange event. In the call to XmlReader.Create() I’ve used two constants, one for the base URL to the Yahoo Maps API and the second for my Application ID. Obviously you’d need to set the Application ID to reflect the one you received earlier.

Presumably, the request went through without a hitch and you are now in possession of a successful XML response. I know, I know, there is absolutely no validation going on here or effort to do any sort of error checking. It’d probably wouldn’t hurt to make sure that the zip code was in a usable format, that the XML response was actually valid, etc. Maybe we can touch on that in a future article.

So, anyways, we now have an XmlReader object and we are going to read it line-by-line in a While loop (line 7), during which a couple of If statements look to see if we’ve come across the city and state elements in the response. I also check to make sure that our XmlReader is at an XML start element (i.e. <City> vs. </City>) so that everything goes smoothly. I don’t know if this is the best way of going about this, but it works, so I didn’t complain ;)

We created our CityState object at the beginning of the function and end up setting its two properties during the read of the XML response. Assuming all went well, the client javascript is returned a CityState object with the names we’re after. And, as we discussed earlier, we simply take that data and slightly reformat it and then add it to our page, which is what we set out to do!