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

This is the third part of a series of articles on ASP.NET AJAX and JSON. If you haven't read part 1 and part 2 of this series, you are strongly advised to do so before going on to read this article.

Let's take what we've seen so far on JSON and ASP.NET one step further, and discover how we can leverage the ASP.NET AJAX infastructure to make the managing of an AJAX application even sleeker. Now, let's imagine that we want to elaborate the JSON response returned from the web service before sending it back to the client.

This can prove useful for many a reason, for example, to enrich the returned object with properties generated by complex elaborations, which we would rather do on the server than on the client.

In fact, what we will see today, is how to add a Stability Class property, computed used the Pasquill method for categorizing atmospheric turbulence, to the weather observation data returned from the Geonames web service we have come to know in my previous articles. I want to point out that this is merely an example of a complex elaboration that would generally take place on the server rather than on the client, but it could be substituted with any kind of data transformation or elaboration that you would need to apply to the data before sending it back to the client.

Now, in order to do this, we might modify the returned JSON object directly as a string, but you can imagine how cumbersome and error-prone this process would become if the elaborations to be made were less than trivial. We need some more flexibility. And here's where the ASP.NET AJAX infrastructure comes to help us again.

What we want to do first, is convert the JSON response to a .NET object, in order to pave the way for the elaborations that we are going to make. We can do this in a straightforward way, leveraging the .NET JavaScriptSerializer class, which can be found in the System.Web.Script.Serialization namespace. But before starting the actual deserialization process, we need to make some preparation. First of all, we need to create the class we want our JSON object to be shaped into:

 

public class WeatherObservation
{
   
public string clouds { get; set; }
   
public string observation { get; set; }
   
public string weatherCondition { get; set; }
   
public int windDirection { get; set; }
   
public int windSpeed { get; set; }
   
public string ICAO { get; set; }
   
public int elevation { get; set; }
   
public string countryCode { get; set; }
   
public double lat { get; set; }
   
public double lng { get; set; }
   
public int temperature { get; set; }
   
public int dewPoint { get; set; }
   
public int humidity { get; set; }
   
public string stationName { get; set; }
   
public string datetime { get; set; }
   
public int hectoPascAltimeter { get; set; }
   
public string stabilityClass { get; set; }
}

 

As you can see, the properties that we added to the class map directly to those present in the returned JSON object. (See here if you haven't read my previous post and you want to see how the returned JSON string looks like). Also notice that this is the place where we define the additional properties that we want to send to the client along with the original ones; in this case, we added the stabilityClass property.

NOTE: You might notice that we used Automatic Properties, a new language feature introduced with C# 3.0, to make our code more compact and readable (let alone saving a lot of keystrokes ;) ). You can read more on Automatic Properties on Scott Guthrie's blog. If you are not using C# 3.0 yet, the only thing you need to do in order to make this work, is to write basic properties or public fields rather than Automatic Properties.

Actually, if you look at the returned JSON string closely, you'll find out that the weather observation is not returned as a top-level object, but is itself the content of the top-level weatherObservation property. This might seem redundant at first, but this schema is due to fact that more than one weather observation can be returned from the Web Service if desired, and in that case the weatherObservations property would contain an array of observations. Anyway, we can overlook this detail for the moment; all we need to do now to make everything work smoothly, is define a new class, containing a weatherObservation property:

 

public class SingleWeatherObservation
{
   
public WeatherObservation weatherObservation { get; set; }
}

 

Now we're ready to modify our GetWeatherByLocation method.

We'll add a call to the Deserialize<T>() method of the JavaScriptSerializer class. This method will return an object of type T, containing the deserialized data, which we will use for all our subsequent computations. In particular, we'll pass this object to our ComputeStabilityClasses() method, in order to compute the suggested Stability Classes. Later, we'll use this value to set the stabilityClass property of our object. Let's see how the code looks like:

 

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();
    }

   
JavaScriptSerializer ser = new JavaScriptSerializer();
   
SingleWeatherObservation observation =
                     ser.Deserialize<SingleWeatherObservation>(jsonResponse);
    observation.weatherObservation.stabilityClass =
                      ComputeStabilityClasses(observation.weatherObservation);
   
return ser.Serialize(observation);
}
private static string ComputeStabilityClasses(WeatherObservation weatherObservation)
{
   
// ... code for computing the Stability Class
}

 

As you can see, the last thing we do is serialize our object back to a JSON string before handing it back to the caller (which happens to be our proxy Web Service). This is perfectly legitimate, but is an unnecessary step that can be avoided, now that we are working with .NET objects. In fact, the AJAX framework can take the burden of serializing and deserializing .NET objects to and from JSON onto its shoulders, in order to make the process of calling ASP.NET Script-enabled Web Services completely transparent to the user. Let's see how to do this.

First of all, we need to make some little changes to our GetWeatherByLocation() method in order to make it return our .NET object rather than its JSON-serialized counterpart; we'll have to change its signature, and return our observation object without serializing it:

 

public static SingleWeatherObservation GetWeatherByLocation(double lat, double lng)
{
    //.... Same code as above
    return observation;
}

 

Then we'll have to propagate this change to the caller method, i.e. the Web Service's GetWeatherByLocation() method:

 

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 SingleWeatherObservation GetWeatherByLocation(double lat, double lng)
        {
           
return GeonamesWeather.GetWeatherByLocation(lat, lng);
        }
    }
}

 

You can see that the Web Method returns a SingleWeatherObservation object rather than a string containing its JSON representation. Now, we'll have to make some little changes to the client code in order to reflect the changes made to the server side.

function callService()
{
   
var latitude = $get('txtLat').value;
   
var longitude = $get('txtLng').value;
    Mashup.WeatherService.GetWeatherByLocation(latitude, longitude, 
                                     GetWeather_success, onFailed);
}
function GetWeather_success(e)
{
   
//var result = Sys.Serialization.JavaScriptSerializer.deserialize(e, true);
    var result = 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();
}

 

Nothing has changed from what we have seen previously, except for the first line in GetWeather_success(e); we don't need to explicitly deserialize the argument received from the Web Service call anymore, as this is done under the hood for us by the AJAX framework. We can assume that e is a Javascript object that closely mimics the structure of the .NET object returned from the Web Method.

Notice that by default, JavaScriptSerializer supports most usual data types. However, you can serialize and deserialize types that are not natively supported by JavaScriptSerializer by implementing custom converters using the JavaScriptConverter  class, and then register the converters by using the RegisterConverters  method. For more information, consult this page of the ASP.NET AJAX Documentation, or have a look at this interesting article by Chris Pietschmann

As you can see, we have attained a completely transparent communication between server and client code. All the details of serialization / deserialization to and from JSON are hidden from our view by the ASP.NET AJAX framework.

 

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

Enjoy!

Marco

 

Technorati Tag: , , , , , ,

kick it on DotNetKicks.com Digg!

Posted on Wednesday, October 31, 2007 6:32 PM ASP.NET , Javascript , C# | Back to top


Comments on this post: JSON in ASP.NET Ajax: Part 3. Server side deserialization and elaboration of JSON data returned from a Web Service.

# re: JSON in ASP.NET Ajax: Part 3. Server side deserialization and elaboration of JSON data returned from a Web Service.
Requesting Gravatar...
You wrote:

"Actually, if you look at the returned JSON string closely, you'll find out that the weather observation is not returned as a top-level object, but is itself the content of the top-level weatherObservation property. This might seem redundant at first, but this schema is due to fact that more than one weather observation can be returned from the Web Service if desired, and in that case the weatherObservations property would contain an array of observations. Anyway, we can overlook this detail for the moment; all we need to do now to make everything work smoothly, is define a new class, containing a weatherObservation property:"

How would you deserialize this on the server if you *couldn't* overlook the array? What if you received a JSON string that looks like:

{"postalCodes":[{"adminName2":"Olmsted","adminCode2":"109","adminCode1":"MN","postalCode":"55901","countryCode":"US","lng":-92.5088,"placeName":"Rochester","lat":44.0689,"adminName1":"Minnesota"},{"adminName2":"Olmsted","adminCode2":"109","adminCode1":"MN","postalCode":"55905","countryCode":"US","lng":-92.466826,"placeName":"Rochester","lat":44.022513,"adminName1":"Minnesota"},{"adminName2":"Olmsted","adminCode2":"109","adminCode1":"MN","postalCode":"55903","countryCode":"US","lng":-92.540929,"placeName":"Rochester","lat":43.996613,"adminName1":"Minnesota"},{"adminName2":"Olmsted","adminCode2":"109","adminCode1":"MN","postalCode":"55906","countryCode":"US","lng":-92.405294,"placeName":"Rochester","lat":44.107815,"adminName1":"Minnesota"},{"adminName2":"Olmsted","adminCode2":"109","adminCode1":"MN","postalCode":"55960","countryCode":"US","lng":-92.373869,"placeName":"Oronoco","lat":44.084556,"adminName1":"Minnesota"}]}

(Incidentally, you can get a JSON string like that from calling another geonames webservice at "http://ws.geonames.org/findNearbyPostalCodesJSON?lat={0}&lng={1}")

I've tried everything I can think of to deserialize that on the server and I'm having no luck. Another person posted a similar JSON string and his difficulty deserializing it on the server to Microsoft as a bug (see https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=322252), but the response so far did not resolve the issue from my perspective because I can't control what the returned string looks like. The most frustrating part of this is that I can deserialize the string without difficulty on the client, but I need to add some computed information to the data that is being returned from the server.

Any insight you can provide would be most appreciated!
Left by Ed on Jan 24, 2008 7:37 PM

# re: JSON in ASP.NET Ajax: Part 3. Server side deserialization and elaboration of JSON data returned from a Web Service.
Requesting Gravatar...
Evidently (from what others have said) using Microsofts provided methods for deserializing JSON you are not able to deserialize full datasets on the server... you may only work with single records.

This in most cases makes JSON as implemented in .NET useless to me.
Left by Scott D. Sullivan-Reinhart on Oct 19, 2008 4:34 PM

# re: JSON in ASP.NET Ajax: Part 3. Server side deserialization and elaboration of JSON data returned from a Web Service.
Requesting Gravatar...
Wonderful!
Thanks for sharing the tech!
Left by Lance on Oct 30, 2008 5:39 AM

# re: JSON in ASP.NET Ajax: Part 3. Server side deserialization and elaboration of JSON data returned from a Web Service.
Requesting Gravatar...
Great tutorial! Shame you stopped there. :)
Left by collinsadrian on Dec 09, 2009 9:05 AM

Your comment:
 (will show your gravatar)


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