So I'm sitting here, trying to come up with something interesting to write about for a Silverlight tutorial, when an idea struck - go look at our old Flex tutorials! The Fattest spent a bunch of time coming up with great examples of how to accomplish various tasks in Flex - and supposedly anything that Flex can do, Silverlight can do too. I went and looked, and I didn't have to look very far. In the very first tutorial The Fattest wrote on Flex here at SOTC ( Flex & PHP Tutorial - Transmitting data using JSON), I ran into something I didn't know how to do in Silverlight - use JSON.
And so that is what this tutorial is going to be about - requesting and parsing JSON data into C# objects, and displaying them. If you clicked the link to the old tutorial above, the example below (if you have Silverlight 2 installed) might look eerily familiar. It is a Silverlight replication of the Flex example in that tutorial - in fact, it is even requesting the data from the exact same backend PHP page that the old tutorial uses for its example (and the data is being returned in the same way as well)!
Ok, now that you have had a chance to play with the simple example, let's dive right in. I'm not going to repeat the PHP code - if you want to take a look at it, it is covered in the old tutorial. But before we dive into the C# and XAML, let's have a look at the JSON that that PHP page returns. There are two types of requests, one for an Employee, and one for a Manager. The returned data for requesting an Employee looks like this:
"first_name":"Jacob",
"last_name":"D\u00e4oe\u00f6",
"email":"fake@email.com",
"address":"5555 Some Street City, State 55555"
}
And the Manager looks like this:
"title":"Office Manager",
"employees":
[
{
"first_name":"Joe",
"last_name":"Doe",
"email":"joe.doe@email.com",
"address":"5424 Some Street City, State 55555"
},
{
"first_name":"Bob",
"last_name":"Doe",
"email":"bob.doe@email.com",
"address":"1414 Some Street City, State 55555"
},
{
"first_name":"Kevin",
"last_name":"Doe",
"email":"kevin.doe@email.com",
"address":"6123 Some Street City, State 55555"
}
],
"first_name":"Manager",
"last_name":"Doe",
"email":"manager.doe@email.com",
"address":"5534 Some Other Street City, State 55555"
}
Now, unlike Actionscript or Javascript or PHP, C# is a strictly typed language, which makes dealing with JSON a little bit more painful. You have to know exactly what type of data is coming across the wire and funnel it into the appropriate type of object, otherwise stuff doesn't match and explosions occur. So in this case we need to create two different C# classes, a Person and a Manager:
{
public string first_name { get; set; }
public string last_name { get; set; }
public string email { get; set; }
public string address { get; set; }
}
public class Manager : Person
{
public string title { get; set; }
public Person[] employees { get; set; }
}
What is extremely important here is that the names of these fields match exactly with what the field name in the JSON encoded data is - otherwise the object won't populate correctly. So we end up with a Person class that has 4 appropriately named string fields, and a Manager class that derives from Person and adds two more fields, a title field and an array of Person objects.
Ok, let's move on to the user interface, and we can get back to actually populating those objects in a minute. Here is the XAML for the interface:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:data=
"clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
Width="500" Height="Auto" Name="This">
<UserControl.Resources>
<Style TargetType="TextBox" x:Key="BoxStyle">
<Setter Property="Width" Value="200" />
<Setter Property="Margin" Value="3" />
<Setter Property="IsReadOnly" Value="True" />
</Style>
<Style TargetType="TextBlock" x:Key="BlockStyle">
<Setter Property="VerticalAlignment"
Value="Center" />
<Setter Property="Margin" Value="3" />
</Style>
<Style TargetType="Button" x:Key="BtnStyle">
<Setter Property="HorizontalContentAlignment"
Value="Center" />
<Setter Property="Margin" Value="4 2 4 5" />
<Setter Property="Width" Value="100" />
</Style>
</UserControl.Resources>
<Grid Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.ColumnSpan="4" Grid.RowSpan="8"
BorderThickness="1" BorderBrush="Black" />
<TextBlock Grid.Row="0" Grid.Column="1"
Style="{StaticResource BlockStyle}">
Name
</TextBlock>
<TextBox Grid.Row="0" Grid.Column="2"
Style="{StaticResource BoxStyle}"
x:Name="_NameBox" />
<TextBlock Grid.Row="1" Grid.Column="1"
Style="{StaticResource BlockStyle}">
</TextBlock>
<TextBox Grid.Row="1" Grid.Column="2"
Style="{StaticResource BoxStyle}"
x:Name="_EmailBox" />
<TextBlock Grid.Row="2" Grid.Column="1"
Style="{StaticResource BlockStyle}">
Address
</TextBlock>
<TextBox Grid.Row="2" Grid.Column="2"
Style="{StaticResource BoxStyle}"
x:Name="_AddressBox" />
<TextBlock Grid.Row="3" Grid.Column="1"
Style="{StaticResource BlockStyle}">
Title
</TextBlock>
<TextBox Grid.Row="3" Grid.Column="2"
Style="{StaticResource BoxStyle}"
x:Name="_TitleBox" />
<TextBlock Grid.Row="4" Grid.Column="1"
Style="{StaticResource BlockStyle}">
Has Employees
</TextBlock>
<TextBox Grid.Row="4" Grid.Column="2"
Style="{StaticResource BoxStyle}"
x:Name="_HasEmpBox" />
<TextBlock Grid.Row="5" Grid.Column="0"
Style="{StaticResource BlockStyle}">
Employees:
</TextBlock>
<data:DataGrid Grid.Row="6" Grid.ColumnSpan="4"
Margin="3" x:Name="_EmpGrid"
AutoGenerateColumns="False"
Height="200" IsReadOnly="True">
<data:DataGrid.Columns>
<data:DataGridTextColumn Header="First Name"
Binding="{Binding first_name}"/>
<data:DataGridTextColumn Header="Last Name"
Binding="{Binding last_name}"/>
<data:DataGridTextColumn Header="Email"
Binding="{Binding email}"/>
<data:DataGridTextColumn Header="Address"
Binding="{Binding address}"/>
</data:DataGrid.Columns>
</data:DataGrid>
<StackPanel Grid.Row="7" Grid.Column="1"
Grid.ColumnSpan="2"
Orientation="Horizontal"
HorizontalAlignment="Center">
<Button Style="{StaticResource BtnStyle}"
Content="Get Employee"
Click="GetEmployeeClick"/>
<Button Style="{StaticResource BtnStyle}"
Content="GetManager"
Click="GetManagerClick"/>
</StackPanel>
</Grid>
</UserControl>
Mostly your standard XAML, but there are a couple things to note. As you might have noticed, I'm pulling out Silverlight 2's DataGrid. To get the DataGrid, you have to add a reference in your Visual Studio project to System.Windows.Controls.Data, and add an xmlns line to your XAML file (you can see the one for this xaml a couple lines from the top). As data grids go, this particular instantiation is pretty simple - 4 readonly columns with some bindings. The bindings are to the various fields on a Person object - this means that when we add some Person objects as items in the DataGrid everything will display nicely.
But overall, pretty uninteresting XAML (isn't it crazy just how much _room_ uninteresting XAML can take up?). So back to the C# code behind, and specifically, the two methods attached to the Click events on the buttons at the bottom of the XAML - GetEmployeeClick and GetManagerClick. First, let's walk through GetEmployeeClick:
{
ClearFields();
WebClient wc = new WebClient();
wc.OpenReadCompleted += GetEmployeeOpenReadCompleted;
wc.OpenReadAsync(new Uri(_CallbackUrl + "?getPerson=true"));
}
First, we call ClearFields, which, as you might expect, just clears all the various fields in the UI:
{
_NameBox.Text = "";
_EmailBox.Text = "";
_AddressBox.Text = "";
_TitleBox.Text = "";
_HasEmpBox.Text = "";
_EmpGrid.ItemsSource = null;
}
Then we create a WebClient instance and attach to the OpenReadCompleted event. This event will be fired when the request completes, and the event args will hold the resulting data from the server. Finally, we call OpenReadAsync on the appropriate url (_CallbackUrl is a private field that holds the url of the callback PHP page). OpenReadAsync immediately returns (since the request is occurring asynchronously), and we wait for OpenReadCompleted to fire and call our method GetEmployeeOpenReadCompleted:
OpenReadCompletedEventArgs e)
{
if (e.Error != null || e.Cancelled)
{
MessageBox.Show("Error Getting Employee");
return;
}
DataContractJsonSerializer serializer =
new DataContractJsonSerializer(typeof(Person));
Person person = serializer.ReadObject(e.Result) as Person;
_NameBox.Text = person.first_name + " " + person.last_name;
_EmailBox.Text = person.email;
_AddressBox.Text = person.address;
_TitleBox.Text = "No Title";
_HasEmpBox.Text = "No";
_EmpGrid.ItemsSource = null;
}
This is where we get to do some JSON parsing (as long as the request didn't fail or get canceled). First we create an instance of DataContractJsonSerializer specifically for the Person class. DataContractJsonSerializer is a class in the System.Runtime.Serialization.Json namespace, and to get access to it you will need to add a reference to the System.ServiceModel.Web.dll in your Visual Studio project. Once we have that serializer, getting the Person object is pretty easy - we just call ReadObject on the Result (which is just a Stream). That call will return an object, which we can then cast to a Person. Using that new Person object, we can populate all the various fields.
The GetManagerClick method follows a very similar code path - the main differences are the callback url query string and the type of object being de-serialized:
RoutedEventArgs e)
{
ClearFields();
WebClient wc = new WebClient();
wc.OpenReadCompleted += GetManagerOpenReadCompleted;
wc.OpenReadAsync(new Uri(_CallbackUrl + "?getManager=true"));
}
private void GetManagerOpenReadCompleted(object sender,
OpenReadCompletedEventArgs e)
{
if (e.Error != null || e.Cancelled)
{
MessageBox.Show("Error Getting Manager");
return;
}
DataContractJsonSerializer serializer =
new DataContractJsonSerializer(typeof(Manager));
Manager mgr = serializer.ReadObject(e.Result) as Manager;
_NameBox.Text = mgr.first_name + " " + mgr.last_name;
_EmailBox.Text = mgr.email;
_AddressBox.Text = mgr.address;
_TitleBox.Text = mgr.title;
_HasEmpBox.Text = mgr.employees.Length > 0 ? "Yes" : "No";
_EmpGrid.ItemsSource = mgr.employees;
}
Almost identical except for a few key spots. Oh, and in case you were wondering about populating the DataGrid, it is as easy as assigning the array of Person objects on the Manager as the ItemsSource on the DataGrid.
Ok, let's throw all the C# code together in a single block:
using System.Net;
using System.Runtime.Serialization.Json;
using System.Windows;
using System.Windows.Controls;
namespace SilverlightJSON
{
public partial class Page : UserControl
{
private string _CallbackUrl;
public Page(string callbackUrl)
{
_CallbackUrl = callbackUrl;
InitializeComponent();
}
private void GetEmployeeClick(object sender,
RoutedEventArgs e)
{
ClearFields();
WebClient wc = new WebClient();
wc.OpenReadCompleted +=
GetEmployeeOpenReadCompleted;
wc.OpenReadAsync(new Uri(_CallbackUrl
+ "?getPerson=true"));
}
private void ClearFields()
{
_NameBox.Text = "";
_EmailBox.Text = "";
_AddressBox.Text = "";
_TitleBox.Text = "";
_HasEmpBox.Text = "";
_EmpGrid.ItemsSource = null;
}
private void GetEmployeeOpenReadCompleted(object sender,
OpenReadCompletedEventArgs e)
{
if (e.Error != null || e.Cancelled)
{
MessageBox.Show("Error Getting Employee");
return;
}
DataContractJsonSerializer serializer =
new DataContractJsonSerializer(typeof(Person));
Person person = serializer.ReadObject(e.Result)
as Person;
_NameBox.Text = person.first_name
+ " " + person.last_name;
_EmailBox.Text = person.email;
_AddressBox.Text = person.address;
_TitleBox.Text = "No Title";
_HasEmpBox.Text = "No";
_EmpGrid.ItemsSource = null;
}
private void GetManagerClick(object sender,
RoutedEventArgs e)
{
ClearFields();
WebClient wc = new WebClient();
wc.OpenReadCompleted +=
GetManagerOpenReadCompleted;
wc.OpenReadAsync(new Uri(_CallbackUrl
+ "?getManager=true"));
}
private void GetManagerOpenReadCompleted(object sender,
OpenReadCompletedEventArgs e)
{
if (e.Error != null || e.Cancelled)
{
MessageBox.Show("Error Getting Manager");
return;
}
DataContractJsonSerializer serializer =
new DataContractJsonSerializer(typeof(Manager));
Manager mgr = serializer.ReadObject(e.Result)
as Manager;
_NameBox.Text = mgr.first_name
+ " " + mgr.last_name;
_EmailBox.Text = mgr.email;
_AddressBox.Text = mgr.address;
_TitleBox.Text = mgr.title;
_HasEmpBox.Text =
mgr.employees.Length > 0 ? "Yes" : "No";
_EmpGrid.ItemsSource = mgr.employees;
}
}
public class Person
{
public string first_name { get; set; }
public string last_name { get; set; }
public string email { get; set; }
public string address { get; set; }
}
public class Manager : Person
{
public string title { get; set; }
public Person[] employees { get; set; }
}
}
And that is it! Next step would be sending data back to the server using JSON, but that is a topic for next time. You can download a zip file containing this Silverlight project here if you would like to play around with it on your own. And as always, if there are any questions or comments, leave them below and I will do my best to answer them.
06/18/2009 - 01:42
for some reason the method id not working with silverlight. I my self am having problems getting this to execute.
06/18/2009 - 02:05
what is responsible for decoding the JSON on the silverlight end?
06/18/2009 - 06:53
JSON deserialization comes with the subset of the .NET framework packaged with Silverlight. It's part of the System.Runtime.Serialization.Json namespace.
06/19/2009 - 09:15
Did you get this tutorial to work...
06/19/2009 - 10:04
It worked when it was written. I noticed yesterday that the example app is now broken. A created a bug report for it so hopefully it will get addressed soon.
06/19/2009 - 12:43
Yup, it is fixed now. The root issue was a missing clientaccesspolicy.xml file.
06/21/2009 - 22:06
I downloaded the zip file and tried using it on an apache server. i placed a clientaccesspolicy.xml file in the same directory as the php file but i kept getting the "error getting employee" message is there anything special that has to be placed in the clientaccess.xml file? Or anything special to be done on the apache server?
06/21/2009 - 23:02
I'm getting a "System.Security.SecurityException" error. But as far as i can tell everything is in the right place and no changes were made to the code that you have here.
06/22/2009 - 07:41
the clientaccesspolicy.xml file does not go in the same directory as the php file - it should be placed at the root of the website. If you look at your apache logs, you should be able to see the application trying to request it. Here is what mine looks like:
<access-policy>
<cross-domain-access>
<policy>
<allow-from http-request-headers="*">
<domain uri="*"/>
</allow-from>
<grant-to>
<resource path="/" include-subpaths="true"/>
</grant-to>
</policy>
</cross-domain-access>
</access-policy>
I also have a crossdomain.xml file (either should work):
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
<allow-http-request-headers-from domain="*" headers="*"/>
</cross-domain-policy>
06/23/2009 - 08:31
Thanks alot Tallest, i got it to working...i was overlooking the way the page was being displayed, it was not coming from the server so it always returned a null result. Thanks for your help.
06/19/2009 - 09:28
I think the problem is the url that is sent or not sent to the php file...any one knows how to send url info from silverlight?
Add Comment
[language] [/language]
Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]
See here for supported languages.
Javascript must be enabled to submit anonymous comments - or you can login.