WEBSITE

Accessing XML Data in Silverlight

9/15/2010 9:59:54 AM
1. Problem

You need to work with XML data in Silverlight using the XmlReader object as well as work with XML data in Silverlight using LINQ, because you would like to work with the XML data as a collection of objects.

2. Solution

Use the XmlReader object along with the necessary objects in the related System.Xml namespace to retrieve XML data. Use the language features first introduced in C# 3.0 and the System.Xml and System.Linq namespaces to query XML data.

3. How It Works

There are two ways to parse XML data in Silverlight: the XmlReader class and LINQ to XML, which is one of the new technologies that became available in .NET Framework 3.5 and later that we cover below.

The XmlReader class is a fast-forward-only, noncaching XML parser. For processing large XML files, XmlReader is better suited than LINQ to XML for performance reasons.

The Silverlight XmlReader works in a similar manner as the XmlReader in the full version of the .NET Framework. Visit this site for details on the differences between the .NET Framework and the .NET Framework for Silverlight versions of XmlReader: msdn.microsoft.com/en-us/library/cc189053(VS.95).aspx

What is great about Silverlight is that it is a rich subset of the full .NET Framework 3.5 and that it includes LINQ. There are many web sites, blogs, and books that cover LINQ, so we won't dive into all the details here.

NOTE

A great resource on LINQ is Joseph C. Rattz Jr.'s Pro LINQ: Language Integrated Query in C# 2008 (Apress, 2007).

The goal of the second part of this recipe is to show how to retrieve XML data using an XmlResolver, in this case the XmlXapResolver, which extracts XML from the xap, and then load the XML data into an XDocument object.

You call XDocument.Load(XmlReader) to load the contents into an XDocument so that it can be queried using LINQ. The XDocument class, located in the System.Xml.Linq namespace, is the key object in LINQ to XML functionality.

4. The Code

The XmlReader class can be used to read XML data from the IsolatedStorage file system as well as from streams retrieved via the network just like in the full .NET Framework. A unique Silverlight ability that you take advantage of in this recipe is to use an XmlXapResolver to retrieve XML data embedded into the application's .xap file, which is the container for Silverlight applications. An XML resolver in .NET resolves, or evaluates, external XML resources. An XmlUrlResolver is used to resolve the Url location passed into XmlReader.Create. The XmalXapResolver looks for the name passed into XmlReader.Create within the .xap file for the application:

XmlReaderSettings XmlRdrSettings = new XmlReaderSettings();
XmlRdrSettings.XmlResolver = new XmlXapResolver();
XmlReader reader = XmlReader.Create("ApressBooks.xml",
XmlRdrSettings);

The resolver is configured for the XmlReaderSettings object that is passed into the Create method. For more information on the XmlReaderSettings class, refer to the MSDN documentation at msdn.microsoft.com/en-us/library/system.xml.xmlreadersettings(VS.95).aspx

The first step to create the test application for this recipe is to add the XML file to the Silverlight project and set its build action to Content. This puts the XML file into the assembly that is deployed to the web site so that the XmlReader can find it using the XmlXapResolver. Figure 1 shows the test application for this recipe.

Figure 1. Recipe 5's test application UI

When you click the Button titled Retrieve XML, the event handler ButtonReadXML_Click uses the XmlReader and the XmlXapResolver to load the XML into a ListBox control using one line of code:

XmlData.Items.Add(reader.ReadInnerXml());

XmlData is the name of the ListBox control in the XAML for the recipe test application. The XML data is added to the Items collection for the ListBox. Listings 12 have the full code listings for this test application. and

Listing 1. Recipe 5's MainPage.xaml Class File
<UserControl x:Class="Ch02_ProgrammingModel.Recipe2_5.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:data="clr-namespace:Ch02_ProgrammingModel.Recipe2_5"
mc:Ignorable="d"
d:DesignHeight="337" d:DesignWidth="531">
<UserControl.Resources>
<data:ApressBooks x:Key="ApressBooksDS" />
</UserControl.Resources>
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="9*"/>
<RowDefinition Height="44*"/>
<RowDefinition Height="273*"/>
<RowDefinition Height="11*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="13*"/>
<ColumnDefinition Width="246*"/>
<ColumnDefinition Width="257*" />
<ColumnDefinition Width="15*"/>
</Grid.ColumnDefinitions>
<Button Height="27.1" HorizontalAlignment="Left" Margin="8,9,0,8"
VerticalAlignment="Stretch" Grid.Column="1" Grid.Row="1" Content="Retrieve XML"
d:LayoutOverrides="Height" x:Name="ButtonReadXML" Click="ButtonReadXML_Click" Width="106"/>
<ListBox Margin="4" Grid.Column="1" Grid.Row="2" x:Name="XmlData"/>
<Grid Grid.Column="2" Grid.Row="2" Background="White">
<ListBox Margin="4,4,4,4" ItemsSource="{Binding Mode=OneWay, Path=ApressBookList,
Source={StaticResource ApressBooksDS}}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="2,2,2,2">
<TextBlock Text="{Binding Path=ISBN}" Margin="0,0,0,2"/>
<TextBlock Text="{Binding Path=Title}" Margin="0,0,0,2"/>
<TextBlock Width="550" Text="{Binding Path=Description}"
TextWrapping="Wrap" Margin="0,0,0,10"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>


</Grid>
</Grid>
</UserControl>

Listing 2. Recipe 5's MainPage.xaml.cs Class File
using System.Windows;

using System.Windows.Controls;
using System.Xml;

namespace Ch02_ProgrammingModel.Recipe2_5
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}

private void ButtonReadXML_Click(object sender, RoutedEventArgs e)
{

XmlReaderSettings XmlRdrSettings = new XmlReaderSettings();
XmlRdrSettings.XmlResolver = new XmlXapResolver();
XmlReader reader = XmlReader.Create("ApressBooks.xml", XmlRdrSettings);

// Moves the reader to the root element.
reader.MoveToContent();

while (!reader.EOF)
{
reader.ReadToFollowing("ApressBook");
// Note that ReadInnerXml only returns the markup of the node's children
// so the book's attributes are not returned.
XmlData.Items.Add(reader.ReadInnerXml());
}
reader.Close();
}
}
}


For the second part of this recipe, you access XML data with LINQ to XML to avoid working with XmlDocument objects and walking the XML tree. Instead, you create a list of objects containing information on a few Apress books and display the data in a ListBox using a simple data template.

The relevant LINQ to XML functionality is located in the ApressBooks.cs class file. It contains an ApressBooks class that populates a List collection with another custom class called ApressBook, using the ApressBooks.RetrieveData method.

The code in Listing 2 above is a bit more involved so let's go through it line by line. The private member variable backing the public ApressBookList property is declared like this:

private List<ApressBook> _apressBookList;

This section of code is the actual LINQ query:

from b in xDoc.Descendants("ApressBook")
select....

The b variable is simply an anonymous type for retrieving a collection of objects from the XML file that are returned by the call to xDoc.Descendants("ApressBook"). The select keyword in the sample code creates an instance of the ApressBook class, but if you wanted to simply return a collection of strings containing the ISBN, you could use this code:

from b in xDoc.Descendants("ApressBook")
select b.Element("ISBN").Value

Instead, you take advantage of LINQ functionality to streamline creating a collection of ApressBook objects by using this code:

select new ApressBook()
{
Author = b.Element("Author").Value,
Title = b.Element("Title").Value,
ISBN = b.Element("ISBN").Value,
Description = b.Element("Description").Value,
PublishedDate = Convert.ToDateTime(b.Element("DatePublished").Value),
NumberOfPages = b.Element("NumPages").Value,
Price = b.Element("Price").Value,
ID = b.Element("ID").Value
}

The select new code is simply creating an instance of a collection containing ApressBook objects using C# 3.0 object initializer functionality. The value used to set each property for the ApressBook objects is data retrieved from the XML document, such as b.Element("Author").Value. Figure 1 shows the test application for this recipe.


Other  
 
Most View
Group Test: Eight Panels Beyond HD (Part 6) : NEC MultiSync PA301W
Photo Editors: From Professional RAW Tools To Simple Library Management (Part 1)
Microsoft Surface With Windows RT - Not Yet a Game-Changer
A New Point Of View
The Zeppelin Is Finally Downed
Windows 8 : Diagnosis and Recovery - Windows Recovery Environment
Asus PadFone Infinity - An Convertible Phone Goes Full HD And Beyond (Part 3)
Sharepoint 2013 : Restore an Earlier Version of a File or List Item, Approve or Reject a File or List Item
Asus PB278Q – A Monitor With Excellent Resolution
Asus Zenbook Prime UX31A
Top 10
Sharepoint 2013 : Farm Management - Disable a Timer Job,Start a Timer Job, Set the Schedule for a Timer Job
Sharepoint 2013 : Farm Management - Display Available Timer Jobs on the Farm, Get a Specific Timer Job, Enable a Timer Job
Sharepoint 2013 : Farm Management - Review Workflow Configuration Settings,Modify Workflow Configuration Settings
Sharepoint 2013 : Farm Management - Review SharePoint Designer Settings, Configure SharePoint Designer Settings
Sharepoint 2013 : Farm Management - Remove a Managed Path, Merge Log Files, End the Current Log File
SQL Server 2012 : Policy Based Management - Evaluating Policies
SQL Server 2012 : Defining Policies (part 3) - Creating Policies
SQL Server 2012 : Defining Policies (part 2) - Conditions
SQL Server 2012 : Defining Policies (part 1) - Management Facets
Microsoft Exchange Server 2010 : Configuring Anti-Spam and Message Filtering Options (part 4) - Preventing Internal Servers from Being Filtered