Geeks With Blogs

News Welcome to Marco Anastasi and Serena Caruso's technical weblog on Microsoft .NET technologies and related matters!
Juan DoNeblo Just another Dot Net blog by marco anastasi & serena caruso

Now, after the very futile example which accompanied the introduction to JavaScript Object Notation in my last post (by the way, I forgot to mention that this is what the fancy acronym stands for, but I'm sure you already knew that), let's go straight to the fun part, and see how we can leverage the flexibility of JSON (and ASP.NET AJAX, of course) to achieve some less trivial result. When developing a web application, we often need to aggregate data from different sources. You might argue this is no big news, as we've all used web services in a way or the other, and "mashup" is surely not an uncommon word these days. But probably, for most of us, the natural way to think of data returned from a web service call is XML. But XML is not exactly the easiest thing to parse on the client, nor a very succinct format. That's why a number of JSON web services have flourished lately. Probably the first that comes to mind is Google Maps' Geocoder (see here), but there are a lot more. If you are looking for inspiration you can have a look at xmethods. But today, we are focusing on some very, very interesting web methods kindly offered by Geonames.

In fact, as I've always mantained that an example is worth a googolplex words, in this post I'm going to describe a specific problem I had to solve recently, and the rather simple solution I found, using JSON and ASP.NET AJAX.
So, let's get this started: enter

The Problem.

Let's imagine we want to retrieve the most recent weather information for a specific location, given its geographic coordinates. In particular, let's imagine that we let the user input the coordinates directly on the client (for example by clicking on a Virtual Earth map) and we want to visualize the latest weather data observed at the nearest weather station, avoiding the user the annoyance of the much dreaded postback.

The Solution.

Luckily, the findNearByWeatherJSON web service at Geonames does exactly what we need. It takes a longitude and latitude as parameters, and returns a JSON object describing the weather conditions gathered at the nearest observing site.

Although you could directly call this service from the client using classic AJAX patterns (see here for a simple example or  have a look at ajaxpatterns), we'll focus on how to do that by using a proxy ASP.NET web service to avoid security problems and allow for more flexibility, and, as a nice bonus, have more readable and maintainable code. In fact, rather than following a RESTful approach, and call the web service directly from javascript, we'll leverage the ASP.NET AJAX infrastructure and use the more concise and readable RPC-style generated for us by the framework.

But the question is: how? When faced with the necessity to create a server-side proxy for a remote JSON service, one might be tempted to try and find something like the "Add Web Reference" wizard Visual Studio has been spoiling us with for years... well, no luck this time, there's nothing like that for JSON. In fact, while the wizard would help us if we had to consume a SOAP web service, there's nothing like WSDL which can describe the results and help generate a proxy class for JSON services.

But the good news is, we don't need any! In fact, unless you want to elaborate in some way the results returned from the web service (and I'll cover that topic in one of my next posts), all you need to do is create a simple local ASP.NET web service whose only duty, when called, is to make a request to the remote web service and forward the result to the client. So, let's see how to implement all this!

First of all, let's see how to implement the ASP.NET proxy web service:

using System;

using System.Web.Script.Services;

using System.Web.Services;

 

namespace Mashup

{

    /* To allow this Web Service to be called from script using ASP.NET AJAX, 

    * we need to set the following attribute. */

    [ScriptService]

    [WebService(Namespace = "Mashup")]

    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

    public class WeatherService : WebService

    {

        [WebMethod]

        public string GetWeatherByLocation(double lat, double lng)

        {

            return GeonamesWeather.GetWeatherByLocation(lat, lng);

        }

    }

}

You can see that the web service we're publishing takes two parameters, latitude and langitude of a location, and returns a string. We'll see that this string is going to contain exactly the content of the response received from the Geonames Web Service. What we have to notice now, is that we decorated the WeatherService class with the [ScriptService] attribute. This is all we have to do to instruct the ASP.NET engine to create a Javascript helper class that will let us call this method very easily from client code.

NOTE: If you want to have a look at what the generated Javascript proxy class looks like, all you have to do is requesting the .asmx web service from your browser, and append /js to the url. (e.g. http://localhost:2687/JsonTest/WeatherService.asmx/js)

Now, let's delve deeper into the implementation of the GetWeatherByLocation() method:

using System;

using System.Net;

using System.Globalization;

using System.IO;

 

namespace Mashup

{

    public class GeonamesWeather

    {

        private readonly static string FindNearbyWeatherUrl =

            "http://ws.geonames.org/findNearByWeatherJSON?lat={0}&lng={1}";

 

        public static string GetWeatherByLocation(double lat, double lng)

        {

            string formattedUri = String.Format(CultureInfo.InvariantCulture,

                                  FindNearbyWeatherUrl, lat, lng);

 

            HttpWebRequest webRequest = GetWebRequest(formattedUri);

            HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();

            string jsonResponse = string.Empty;

            using (StreamReader sr = new StreamReader(response.GetResponseStream()))

            {

                jsonResponse = sr.ReadToEnd();

            }

            return jsonResponse;

        }

 

        private static HttpWebRequest GetWebRequest(string formattedUri)

        {

            // Create the request’s URI.

            Uri serviceUri = new Uri(formattedUri, UriKind.Absolute);

 

            // Return the HttpWebRequest.

            return (HttpWebRequest)System.Net.WebRequest.Create(serviceUri);

        }

    }

}

As you can see, what the GetWeatherByLocation() method does is simply act as a proxy. We call the remote web service in a RESTful manner, retrieve the response, write it into a string and finally return the string to the caller method. The response returned from the findNearByWeatherJSON service will be something like this:

"{"weatherObservation":{

    "clouds":"few clouds",

    "weatherCondition":"n/a",

    "observation":"LICC 231720Z 29009KT 9999 FEW035 SCT070 16/07 Q1009",

    "windDirection":290,

    "ICAO":"LICC",

    "elevation":17,

    "countryCode":"IT",

    "lng":15.05,

    "temperature":"16",

    "dewPoint":"7",

    "windSpeed":"09",

    "humidity":55,

    "stationName":"Catania / Fontanarossa",

    "datetime":"2007-10-23 17:20:00",

    "lat":37.4666666666667,

    "hectoPascAltimeter":1009 }}"

NOTE: Carriage returns have been inserted only to improve readability, but the response stream won't contain any.

The similarity between this string and a Javascript object is quite evident. We'll see in a moment how to leverage this - not so coincidental - fact.

Now, our server infrastructure is almost complete. The last step we have to take is referencing the Web Service we just created from the page that is actually going to use it.

To do this, we simply add a ScriptManager to the page, and add a reference to the web service in its <Services> section:

<asp:ScriptManager ID="ScriptManager1" runat="server">

    <Services>

        <asp:ServiceReference Path="~/WeatherService.asmx" />

    </Services>

</asp:ScriptManager>

Now we can start to set up a very simple test page to try our flaming new web service!

For now, we'll only add a couple of textboxes to input latitude and longitude, and a button to start the Web Request. We'll do it AJAX style, so we'll use the HTML controls rather than their heavier ASP.NET counterparts. We'll also add a div where we're going to visualize the weather info retrieved from the web service. Note that we are giving id's to all the controls so that we can reference them from the Javascript code that we are going to write.

<label for="txtLat">Latitude:</label><input type="text" id="txtLat" />

<label for="txtLng">Longitude:</label><input type="text" id="txtLng" />

<input type="button" value="Get Weather Info" onclick="callService();" />

<div id="divResult">

</div>

Now all that's left to do is write the Javascript code to wire up everything.

We'll start by implementing the callService() function that will be fired when clicking on the button. Please note that in a real scenario, clicking the button would trigger some sort of validation of the input data. We won't do any of that here in order to keep the code as simple and streamlined as possible.

function callService() { 
    var latitude = $get('txtLat').value; 
    var longitude = $get('txtLng').value; 
    Mashup.WeatherService.GetWeatherByLocation(latitude, longitude,
    GetWeather_success, onFailed); 
}

Let's see what we are doing here. First of all we are creating two variables, latitude and longitude, which contain the value of the two textboxes.

NOTE: Those of you who haven't put their hands on the client side ASP.NET AJAX library yet, might wonder what the $get function does. Actually, not much, except sparing you some keystrokes, as it is a mere shortcut for the Javascript getElementById function. In particular, when called passing only one parameter, the $get alias resolves to document.getElementById, while when called like this: $get(id, element), it resolves to element.getElementById(id).

After that, we're finally ready to call our proxy service. We do that by calling the Javascript proxy method generated for us care of the ASP.NET AJAX framework. As you can see, we can find our WeatherService.GetWeatherByLocation() method inside the Mashup namespace. Please note we have to use the fully qualified name to invoke the service.

Besides the two parameters defined in the web method signature in the server side code, we see that the generated Javascript method takes some additional parameters: onSuccess, onFailed and userContext. As you have probably already realised, they take the name of two callbacks, one that will be executed upon successful completion of the remote method invocation, the other that will be called in the unfortunate event something went wrong. The third parameter, which we are not using here, would let you pass a custom user context to the callback.

Now, if everything works as expected, the GetWeather_success function will be called (asynchronously) when the web service response is ready, and will get as an argument the web method's return value, in our case, a string containing our JSON-serialized weather observation object.

Now the very last thing we need to do is deserialize such string into a real Javascript object. To do that, we might simply call the Javascript eval() function, as I anticipated in my previous post:

function GetWeather_success(e) { 
    var result = eval('(' + e + ')'); 
    var weatherData = new Sys.StringBuilder(); 
    var line; 
    for(var property in result.weatherObservation) 
    { 
        line = String.format("<b>{0}:</b> {1}<br/>", property, 
        result.weatherObservation[property]); weatherData.append(line); 
    } 
    $get('divResult').innerHTML = weatherData.toString(); 
}
function onFailed() { 
    $get('divResult').innerHTML = 'Something went terribly wrong!'; 
}

 

As I just said, when this function is called, it is passed the web method response, thus e contains the JSON representation of a Javascript object containing all the relevant weather data retrieved from our remote Web Service. As you can see, calling eval() deserializes the string and returns an object we can readily use.

From now on, what we are going to do with this native Javascript object is entirely up to us. Here, as a mere example, I'm enumerating all its properties and displaying them inside the div we created for this purpose.

What's worth noting here is the use of two very interesting and useful ASP.NET AJAX features: the Sys.StringBuilder class that mimics its server-side homonym and greatly improves string concatenation performance over the "+" overload, and the String.format() function, which endows Javascript with the very useful .NET String.Format() method we are all used to.

For those of you concerned with security, as I said earlier, eval() is probably not the safest option to deserialize JSON, so you might decide to resort to a JSON parser (you can find one here on json.org), or once again leverage the power of ASP.NET AJAX, by using the built-in JavaScriptSerializer. If you simply substitute the first line in GetWeather_success() with the following line:

var result = Sys.Serialization.JavaScriptSerializer.deserialize(e, true);

the string returned from the web method will be checked against a regular expression to ensure it is actually a valid JSON string.

Well, that's all for now! I hope that this short introduction to the use of JSON with ASP.NET has been useful to some of you!

You can see a live Demo here or download the Source Code of this project here.

You can find the third and last part of this article here.

Enjoy!

Marco

 

Technorati Tag: , , , ,

kick it on DotNetKicks.com Digg!

Posted on Wednesday, October 24, 2007 2:00 AM ASP.NET , Javascript , C# | Back to top


Comments on this post: JSON in ASP.NET Ajax: Part 2. Calling a remote JSON Web Service from client script

No comments posted yet.
Your comment:
 (will show your gravatar)
 


Copyright © Marco Anastasi & Serena Caruso | Powered by: GeeksWithBlogs.net | Join free