Working with JSON web services in Silverlight and Windows Phone 7

In this post I’ll explain how to retrieve and display data from JSON web services.

As an example I’ll use the REST service from geonames.org.

JSON response of the example looks so:

At first, you should create C# classes (models) which can be mapped to JSON entities.
The first curly braсe indicates that it is an object, and it has 1 property `geonames`. After this property you can see the square bracket indicating an array. This array consists of many objects, and each object has 12 properties.
So we should create a model for the root object, which looks so:

// { geonames: [{}, {}, {}] }
[DataContract]
public class CitiesList
{
	[DataMember(Name = "geonames")]
	public List<City> Cities { get; set; }
}

It is important to add DataContract and DataMember attributes to the class and all its properties. Also you should specify the name of the corresponding JSON property (geonames) inside the DataMember attribute.

Then we should create the model for inner objects (cities). As you already saw, each JSON object has 12 properties, but we don’t need all of them. So we can specify only those proeprties which we need, and .Net deserializer will ignore all other properties.

// { name: "", countrycode: "", population: -1 }
[DataContract]
public class City
{
	[DataMember(Name = "name")]
	public string Name { get; set; }

	[DataMember(Name = "countrycode")]
	public string CountryCode { get; set; }

	[DataMember(Name = "population")]
	public int Population { get; set; }
}

So now all that you need is to create an instance of the HttpWebRequest class, get the response and deserialize it by using the DataContractJsonSerializer class. The deserializer requires the System.Servicemodel.Web and System.Runtime.Serialization references.

You must use the two following methods to perform loading and parsing of JSON.

private void BeginDownloadCitiesRequest()
{
	// create the http request
	HttpWebRequest httpWebRequest = WebRequest.CreateHttp("http://api.geonames.org/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=en&username=vortexwolf");
	httpWebRequest.Method = "GET";
	httpWebRequest.Accept = "application/json";

	// get the response asynchronously
	httpWebRequest.BeginGetResponse(OnGetResponseCompleted, httpWebRequest);
}

private void OnGetResponseCompleted(IAsyncResult ar)
{
	var httpWebRequest = (HttpWebRequest)ar.AsyncState;

	// get the response
	var response = httpWebRequest.EndGetResponse(ar);

	// deserialize json
	var jsonSerializer = new DataContractJsonSerializer(typeof(CitiesList));
	var responseObject = (CitiesList)jsonSerializer.ReadObject(response.GetResponseStream());

	// display on the view
	Deployment.Current.Dispatcher.BeginInvoke(() => OnCitiesDownloaded(responseObject));
}

The code of creating a http request and getting a response will often repeat in different places of the application, so it is preferrable to move this code to a separate class.
I did it so:

public class HttpGetTask<T>
{
    public HttpGetTask(string url, Action<T> onPostExecute)
    {
        this.Url = url;
        this.OnPostExecute = onPostExecute;
    }

    public void Execute()
    {
        if (this.OnPreExecute != null)
        {
            this.OnPreExecute();
        }

        // create the http request
        HttpWebRequest httpWebRequest = WebRequest.CreateHttp(this.Url);
        httpWebRequest.Method = "GET";
        httpWebRequest.Accept = "application/json";

        // get the response asynchronously
        httpWebRequest.BeginGetResponse(OnGetResponseCompleted, httpWebRequest);
    }

    private void OnGetResponseCompleted(IAsyncResult ar)
    {
        var httpWebRequest = (HttpWebRequest)ar.AsyncState;

        // get the response
        HttpWebResponse response;
        try
        {
            response = (HttpWebResponse)httpWebRequest.EndGetResponse(ar);
        }
        catch (WebException)
        {
            this.InvokeOnErrorHandler("Unable to connect to the web page.");
            return;
        }
        catch (Exception e)
        {
            this.InvokeOnErrorHandler(e.Message);
            return;
        }

        if (response.StatusCode != HttpStatusCode.OK)
        {
            this.InvokeOnErrorHandler((int)response.StatusCode + " " + response.StatusDescription);
            return;
        }

        // response stream
        var stream = response.GetResponseStream();

        // deserialize json
        var jsonSerializer = new DataContractJsonSerializer(typeof(T));
        var responseObject = (T)jsonSerializer.ReadObject(stream);

        // call the virtual method
        this.InvokeInUiThread(() => this.OnPostExecute(responseObject));
    }

    private void InvokeOnErrorHandler(string message)
    {
        if (this.OnError != null)
        {
            this.InvokeInUiThread(() => this.OnError(message));
        }
    }

    private void InvokeInUiThread(Action action)
    {
        Deployment.Current.Dispatcher.BeginInvoke(action);
    }

    public string Url { get; private set; }

    public Action<T> OnPostExecute { get; private set; }

    public Action OnPreExecute { get; set; }

    public Action<string> OnError { get; set; }
}

You can execute this task by specifying Url and the OnPostExecute callback. Also you can set other properties. Example:

public class MainViewModel : INotifyPropertyChanged
{
    public MainViewModel()
    {
        Cities = new ObservableCollection<CityViewModel>();

        string url = "http://api.geonames.orgzz/citiesJSON?north=44.1&south=-9.9&east=-22.4&west=55.2&lang=en&username=vortexwolf";
        
        var task = new HttpGetTask<CitiesList>(url, this.OnPostExecute);
        task.OnPreExecute = this.OnPreExecute;
        task.OnError = this.OnError;

        task.Execute();
    }

    private void OnPreExecute()
    {
        this.IsLoading = true;
    }

    private void OnPostExecute(CitiesList responseObject)
    {
        this.OnCitiesDownloaded(responseObject);
        this.IsLoading = false;
    }

    private void OnError(string message)
    {
        MessageBox.Show(message);
        this.IsLoading = false;
    }

    public ObservableCollection<CityViewModel> Cities { get; set; }

    private bool _isLoading;

    public bool IsLoading
    {
        get { return _isLoading; }
        set
        {
            _isLoading = value;
            RaisePropertyChanged("IsLoading");
        }
    }

    private void OnCitiesDownloaded(CitiesList citiesList)
    {
        var cityModels = citiesList.Cities
            .Select(c =>
                    new CityViewModel
                    {
                        Name = c.Name,
                        CountryCode = c.CountryCode,
                        Population = c.Population
                    })
            .ToList();

        cityModels.ForEach(this.Cities.Add);
    }

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    public void RaisePropertyChanged(string propertyName)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

I created the sample application for Windows Phone 7 which retrieves JSON data and displays it on the phone.

The source code of the example you can download here: PhoneJsonTest.zip

Advertisements

31 Responses to Working with JSON web services in Silverlight and Windows Phone 7

  1. shiba says:

    {“total”:47,”movies”:[{“id”:”771242005″,”title”:”Magic Mike”,”year”:2012,”mpaa_rating”:”R”,”runtime”:110,”critics_consensus”:”Magic Mike’s sensitive direction, smart screenplay, and strong performances allows audiences to have their beefcake and eat it too.”,”release_dates”:{“theater”:”2012-06-29″,”dvd”:”2012-10-23″},”ratings”:{“critics_rating”:”Certified Fresh”,”critics_score”:79,”audience_rating”:”Upright”,”audience_score”:63},”synopsis”:”Set in the world of male strippers, Magic Mike is directed by Steven Soderbergh and stars Channing Tatum in a story inspired by his real life. The film follows Mike (Tatum) as he takes a young dancer called The Kid (Pettyfer) under his wing and schools him in the fine arts of partying, picking up women, and making easy money. — (C) Warner Bros.”,”posters”:{“thumbnail”:”http://content8.flixster.com/movie/11/16/66/11166610_mob.jpg”,”profile”:”http://content8.flixster.com/movie/11/16/66/11166610_pro.jpg”,”detailed”:”http://content8.flixster.com/movie/11/16/66/11166610_det.jpg”,”original”:”http://content8.flixster.com/movie/11/16/66/11166610_ori.jpg”},”abridged_cast”:[{“name”:”Channing Tatum”,”id”:”162661835″,”characters”:[“Magic Mike”]},{“name”:”Alex Pettyfer”,”id”:”326298019″,”characters”:[“Adam”,”The Kid”]},{“name”:”Matt Bomer”,”id”:”771077752″,”characters”:[“Ken”]},{“name”:”Joe Manganiello”,”id”:”770800475″,”characters”:[“Big Dick Richie”]},{“name”:”Matthew McConaughey”,”id”:”162652350″,”characters”:[“Dallas”]}],”alternate_ids”:{“imdb”:”1915581″},”links”:{“self”:”http://api.rottentomatoes.com/api/public/v1.0/movies/771242005.json”,”alternate”:”http://www.rottentomatoes.com/m/magic_mike/”,”cast”:”http://api.rottentomatoes.com/api/public/v1.0/movies/771242005/cast.json”,”clips”:”http://api.rottentomatoes.com/api/public/v1.0/movies/771242005/clips.json”,”reviews”:”http://api.rottentomatoes.com/api/public/v1.0/movies/771242005/reviews.json”,”similar”:”http://api.rottentomatoes.com/api/public/v1.0/movies/771242005/similar.json”}}],”links”:{“self”:”http://api.rottentomatoes.com/api/public/v1.0/lists/dvds/upcoming.json?page_limit=1&country=us&page=1″,”next”:”http://api.rottentomatoes.com/api/public/v1.0/lists/dvds/upcoming.json?page_limit=1&country=us&page=2″,”alternate”:”http://www.rottentomatoes.com/dvd/upcoming.json”},”link_template”:”http://api.rottentomatoes.com/api/public/v1.0/lists/dvds/upcoming.json?page_limit={results-per-page}&page={page-number}&country={country-code}”}

    My jason looks like this.but i want only movie name and release date.should i create a class that has all these

    • vortexwolf says:

      You need 3 classes:
      1. class MoviesListModel, 1 property Movies
      2. class MovieItemModel, 2 properties, Title and ReleaseDates
      3. Class ReleaseDatesModel, 2 properties, Theater and Dvd

      The main problem is with the ReleaseDates property. It must be a class, which is quite complicated.

  2. Kumar Gaurav says:

    the source code is unavailable…can u give a working link for it

  3. Pingback: Displaying progress bar while loading JSON from Web Services for Windows Phone 7 « Silverlight, WPF and Windows 8 Development

  4. Can I extract obtained data from citiesList to multiple purpose?
    For example if in each citiesList has image, and I want to display those image in panoramaItem, what should I do?

  5. Hey man i try this code with two JSON’s. With first it was ok, but when i try with another one i get this exception :
    InvalidCastException was unhandled by user code. In this part of code:
    var responseObject = (T)jsonSerializer.ReadObject(stream); Tnx.

  6. tolgaakdgn says:

    I have an error. “The name “BooleanToVisibilityConverter” does not exist in the namespace “clr-namespace:Win8MobileProject.Assets”. I tried lots of thing but I couldn’t fixed it. do you know how can I fix that? Can you help me?

    • vortexwolf says:

      There is the Assets folder in the zip-archive, copy files from there and change their namespaces from `namespace PhoneApp1.Assets` to `namespace Win8MobileProject.Assets`.

  7. Hi Vortexwolf,

    thank you for you tutorial. While downloading and serializing my Jsons work after following your steps, my UI doesn’t change.

    Would you like to have a look at my code?

    private void getMapRecordsFromJson()
    {
    // create the http request
    HttpWebRequest httpWebRequest = WebRequest.CreateHttp(“http://www.myserver.com/json/file.json”);
    httpWebRequest.Method = “GET”;
    httpWebRequest.Accept = “application/json”;

    // get the response asynchronously
    httpWebRequest.BeginGetResponse(OnGetResponseCompleted, httpWebRequest);
    }

    private void OnGetResponseCompleted(IAsyncResult ar)
    {
    var httpWebRequest = (HttpWebRequest)ar.AsyncState;

    // get the response
    var response = httpWebRequest.EndGetResponse(ar);

    // deserialize json
    var jsonSerializer = new DataContractJsonSerializer(typeof(IconList));
    var responseObject = (IconList)jsonSerializer.ReadObject(response.GetResponseStream());

    // display on the view
    Deployment.Current.Dispatcher.BeginInvoke(() => OnIconModelsDownloaded(responseObject));
    }

    private void OnIconModelsDownloaded(IconList list)
    {
    ObservableCollection tmp_coll = new ObservableCollection();
    for(int i=0; i<list.iconList.Count; i++)
    {
    IconModel tmp_model = list.iconList[i];

    tmp_model.IconOpacity = 1.0;
    tmp_model.IconMaxLevel = 22;
    tmp_model.IconMinLevel = 19.5;

    Location x = new Location { Latitude = tmp_model.Latitude_double, Longitude = tmp_model.Longitude_double };
    tmp_model.IconLocation = x;

    tmp_coll.Add(tmp_model);
    }

    IconLocationContainer = tmp_coll;
    }

    I'm using Silverlight Bing Maps Control. The Json file contain Locations and types of Icons. After downloading the data i create the models and load them into a ObservableCollection.

    Is there anything i forgot to consider? Maybe something with the async loading and the ui thread?

  8. Manoj says:

    How to perform authentication(Username and password) for REST api…by following your above sample.

    • vortexwolf says:

      Setting the ‘Authorization’ header should work, as it is described in this answer: http://stackoverflow.com/a/3266465/427225

      • Manoj says:

        this gives me some idea…still there is something i didn’t get it…In that default show me definition not found…instead i try to use Unicode and Consider my username:admin and password:1…how can i pass these values…

    • vortexwolf says:

      It depends on how the server side handles authentication. For example, It may require to go to a separate login page, enter your username and password, than give you a cookie. Ot it can require a header instead of cookies. Or it can take these values from url query string. I can’t say for sure, I need to know the exact website that you use.

    • vortexwolf says:

      So you should look how the `get_players` method checks authorization, where it takes username and password. Then you can try to pass these parameters accordingly.

      • Mano says:

        public void Execute()
        {
        if(this.OnPreExecute!=null)
        {
        this.OnPreExecute();
        }

        HttpWebRequest httpWebRequest = WebRequest.CreateHttp(this.Url);
        //httpWebRequest.Credentials = new NetworkCredential(“userName”, “password”);
        SetBasicAuthHeader(httpWebRequest, userName, password);
        httpWebRequest.Method = “GET”;
        httpWebRequest.Accept = “application/json”;

        httpWebRequest.BeginGetResponse(OnGetResponseCompleted, httpWebRequest);
        }

        public void SetBasicAuthHeader(WebRequest httpWebRequest, String userName, String password)
        {
        string authInfo = userName + “:” + password;
        authInfo = Convert.ToBase64String(Encoding.Unicode.GetBytes(authInfo));
        httpWebRequest.Headers[“Authorization”] = “Basic” + authInfo;
        }

        I’m a newbie to this concepts…This is how i tried to do…but still the username and password is in false, how i have to set username as admin and password as 1…

    • vortexwolf says:

      You should look at php code at ‘index.php/api/api/get_players’. Try to find where authentication is checked and verified in the php code.

  9. Mano says:

    your code helps me to run my code thanks for your work…I already asked a doubt in authentication…by using credential it worked out. And i have a doubt in passing image in that process, consider my JSON format is look like…
    “playerId”: “1”,
    “playerName”: ” Soloman Palaparthy Subhash”,
    “specialization”: “Allrounder”,
    “battingHand”: “R”,
    “bowlingHand”: “R”,
    “bowlingType”: “Fast”,
    “genericId”: “SOLO2548”,
    “homeTeamId”: “136”,
    “eligibleTeams”: “136”,
    “imageUrl”: “17B9EE94-8189-49C9-825A-746D39CD1771”

    and in OnPlayerDownloaded() i followed like,
    playerName = c.playerName,
    Specialization = c.Specialization,
    ImageUrl = c.ImageUrl

    but the image file is downloaded but it doesn’t showed while debugging.

    • vortexwolf says:

      I think that imageUrl should start from “http://” and be a valid url that you can open from a browser.

      • Mano says:

        Thanks for your time and it worked, Is there any similar method Intent which is used in android like in windows. Actually i have a condition, consider there is two pages A and B, the A shows list of players name, while I click some individual player it has navigate to B page and display Individual player profile. I tried, but it displays all players profile list.

  10. Mano says:

    Do you developed tutorial or samples using SOAP API, for windows phone. Because your code are lot helpful to understand.

  11. Matt says:

    Thank you for posting this example app for downloading JSON data. I am working on a school project to build a Windows Phone app to display data retrieved from a web service. The data looks like this:

    [{“oid”:”1″,”fname”:”John”,”lname”:”Doe”},{“oid”:”2″,”fname”:”Mary”,”lname”:”Smith”},{“oid”:”3″,”fname”:”Jimi”,”lname”:”Hendrix”},{“oid”:”4″,”fname”:”Carole”,”lname”:”King”},{“oid”:”5″,”fname”:”John”,”lname”:”Winchester”},{“oid”:”6″,”fname”:”John”,”lname”:”Hurt”},{“oid”:”7″,”fname”:”Rick”,”lname”:”Grimes”},{“oid”:”8″,”fname”:”Haris”,”lname”:”Okic”},{“oid”:”9″,”fname”:”Dino “,”lname”:”Okic”},{“oid”:”10″,”fname”:”Mirza”,”lname”:”Cirkic”},{“oid”:”11″,”fname”:”Peter”,”lname”:”Griffin”},{“oid”:”12″,”fname”:”Adam”,”lname”:”james”},{“oid”:”13″,”fname”:”Adam”,”lname”:”james”}]

    I have borrowed your code as a starting point, and modified the model classes to model the above data. They are as follows:

    {
    [DataContract]
    public class OwnersList
    {
    public List Owners {get; set; }
    }
    }

    {
    [DataContract]
    public class Owner
    {
    [DataMember(Name = “oid”)]
    public string Oid { get; set; }

    [DataMember(Name = “fname”)]
    public string Fname { get; set; }

    [DataMember(Name = “lname”)]
    public string Lname { get; set; }
    }
    }

    I also modified the MainViewModel code for the OnPostExecute callback method as follows:

    private void OnOwnersDownloaded(OwnersList ownersList)
    {
    var ownerModels = ownersList.Owners
    .Select(c =>
    new OwnerViewModel
    {
    Oid = c.Oid,
    Fname = c.Fname,
    Lname = c.Lname
    })
    .ToList();

    ownerModels.ForEach(this.Owners.Add);
    }

    When I run the app, I get an Invalid Casting Exception either when creating the DataContractSerializer object or retrieving the response object. Do you have any ideas why this might be happening?

    • vortexwolf says:

      The issue is that OwnersList is a class while in json it expects an array. You can try to do the following:
      [DataContract]
      public class OwnersList : List<Owner>
      {
      }

      Instead of including an array inside it, the object should become an array by using inheritance.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: