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.
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.