Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]

.pdf
Скачиваний:
107
Добавлен:
16.08.2013
Размер:
29.8 Mб
Скачать

448 C H A P T E R 1 2 X M L

<xs:complexType name="DVDType"> <xs:sequence>

<xs:element name="Title" type="xs:string" /> <xs:element name="Director" type="xs:string" /> <xs:element name="Price" type="xs:decimal" /> <xs:element name="Starring" type="StarringType" />

</xs:sequence>

<xs:attribute name="ID" type="xs:integer" /> <xs:attribute name="Category" type="xs:string" />

</xs:complexType>

<xs:complexType name="StarringType"> <xs:sequence maxOccurs="unbounded">

<xs:element name="Star" type="xs:string"/> </xs:sequence>

</xs:complexType>

</xs:schema>

This schema defines two complex types, representing the list of stars (named StarringType) and the list of DVDs (named DVDType). The structure of the document is defined using an <element> tag.

To validate an XML document against a schema, you use the XmlValidatingReader class. This class is based on the XmlTextReader but includes the ability to verify that the document follows the rules specified in an XSD schema file. The XmlValidatingReader throws an exception (or raises an event) to indicate errors as you move through the XML file.

The first step when performing validation is to import the System.Xml.Schema namespace, which contains types such as XmlSchema and XmlSchemaCollection:

using System.Xml.Schema;

The following example shows how you can create an XmlValidatingReader that uses the DvdList.xsd file and shows how you can use it to verify that the XML in DvdList.xml is valid:

string xmlFile = Server.MapPath("DvdList.xml"); string xsdFile = Server.MapPath("DvdList.xsd");

// Open the XML file.

XmlTextReader r = new XmlTextReader(xmlFile);

//Create the validating reader. XmlValidatingReader vr = new XmlValidatingReader(r); vr.ValidationType = ValidationType.Schema;

//Add the XSD file to the validator. XmlSchemaCollection schemas = new XmlSchemaCollection(); schemas.Add("", xsdFile);

vr.Schemas.Add(schemas);

//Read through the document.

while (vr.Read())

{

//Process document here.

//If an error is found, an exception will be thrown.

}

vr.Close();

C H A P T E R 1 2 X M L

449

Using the current file, this code will succeed, and you’ll be able to access the current node through the XmlValidatingReader object in the same way that you can with the XmlTextReader. However, consider what happens if you make the minor modification shown here:

<DVD ID="A" Category="Science Fiction">

Now when you try to validate the document, an XmlSchemaException (from the System.Xml.Schema namespace) will be thrown, alerting you to the invalid data type—the letter A in an attribute that is designated for integer values.

Instead of catching errors, you can react to the ValidationEventHandler event. If you react to this event, you’ll be provided with information about the error, but no exception will be thrown. To connect an event handler to this event, create a new ValidationEventHandler delegate and assign it to the XmlValidatingReader.ValidationEventHandler event just before you start to read the XML file:

// Connect to the method named MyValidateHandler. vr.ValidationEventHandler += new ValidationEventHandler(ValidateHandler);

The event handler receives a ValidationEventArgs class, which contains the exception, a message, and a number representing the severity:

private void ValidateHandler(Object sender, ValidationEventArgs e)

{

lblInfo.Text += "Error: " + e.Message + "<br />";

}

To try the validation, you can use the XmlValidation.aspx page in the online samples. This page allows you to validate a valid DVD list as well as another version with incorrect data and an incorrect tag. Figure 12-7 shows the result of a failed validation attempt.

Figure 12-7. The validation test page

450 C H A P T E R 1 2 X M L

Displaying XML Content with XSL

Another related standard is XSL (Extensible Stylesheet Language), which is an XML-based language for creating stylesheets. Stylesheets (also known as transforms) are special documents that can be used (with the help of an XSLT processor) to convert your XML documents into other documents. For example, you can use an XSLT stylesheet to transform one type of XML to a different XML structure. Or you could use a stylesheet to convert your data-only XML into another text-based document such as an HTML page, as you’ll see with the next example.

Note Of course, XSL stylesheets shouldn’t be confused with CSS (Cascading Style Sheets), a standard used to format HTML. Chapter 15 discusses CSS.

Before you can perform a transformation, you need to create an XSL stylesheet that defines how the conversion should be applied. XSL is a complex standard—in fact, it can be considered a genuine language of its own with conditional logic, looping structures, and more.

Note A full discussion of XSLT is beyond the scope of this book. However, if you want to learn more, you can consider a book such as Jeni Tennison’s Beginning XSLT (Apress, 2004), the excellent online tutorials at http://www.w3schools.com/xsl, or the standard itself at http://www.w3.org/Style/XSL.

A Basic Stylesheet

To transform the DVD list into HTML, you’ll use the simple stylesheet shown here:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:template match="/">

<html>

<body>

<xsl:apply-templates select="//DVD" /> </body>

</html>

</xsl:template>

<xsl:template match="DVD"> <hr/>

<h3><u><xsl:value-of select="Title" /></u></h3> <b>Price: </b> <xsl:value-of select="Price" /><br/> <b>Director: </b> <xsl:value-of select="Director" /><br/> <xsl:apply-templates select="Starring" />

</xsl:template>

<xsl:template match="Starring"> <b>Starring:</b><br /> <xsl:apply-templates select="Star" />

</xsl:template>

<xsl:template match="*"> <li><xsl:value-of select="." /></li>

</xsl:template>

</xsl:stylesheet>

C H A P T E R 1 2 X M L

451

Every XSL file has a root <stylesheet> element. The <stylesheet> element can contain one or more templates (the sample file has four). In this example, the first <template> element searches for the root element. When it finds it, it outputs the tags necessary to start an HTML page and then uses the <apply-templates> command to branch off and perform processing for any contained <DVD> elements, as follows:

<xsl:template match="/"> <html>

<body>

<xsl:apply-templates select="//DVD" /> </body>

</html>

</xsl:template>

Each time the <DVD> tag is matched, a horizontal line is added, and a heading is created. Information about the <Title>, <Price>, and <Director> tag is extracted and written to the page using the <value-of> command. Here’s the full template:

<xsl:template match="DVD"> <hr/>

<h3><u><xsl:value-of select="Title" /></u></h3> <b>Price: </b> <xsl:value-of select="Price" /><br/> <b>Director: </b> <xsl:value-of select="Director" /><br/> <xsl:apply-templates select="Starring" />

</xsl:template>

Using XslTransform

Using this template and the XslTransform class (contained in the System.Xml.Xsl namespace), you can transform the DVD list into formatted HTML. Here’s the code that performs this transformation and saves the result to a new file:

string xslFile = Server.MapPath("DvdList.xsl"); string xmlFile = Server.MapPath("DvdList.xml"); string htmlFile = Server.MapPath("DvdList.htm"); XslTransform transf = new XslTransform(); transf.Load(xslFile);

transf.Transform(xmlFile, htmlFile);

Of course, in a dynamic web application you’ll want to transform the XML file and return the resulting code directly, without generating an HTML file. To do this you have to create an XPath-Navigator for the source XML file. You can then pass the XPathNavigator to the XslTranform.Transform() method, which returns an XmlTextReader that you can use to access the transformed output.

The following code demonstrates this technique:

// Create an XPathDocument.

XPathDocument xdoc = new XPathDocument(new XmlTextReader(xmlFile));

// Create an XPathNavigator.

XPathNavigator xnav = xdoc.CreateNavigator();

// Transform the XML.

XmlTextReader reader = transf.Transform(xnav, null);

452 C H A P T E R 1 2 X M L

Once you have an XmlTextReader for the output, you can move to the beginning of the stream and render the text to the response stream:

reader.MoveToContent();

Response.Write(reader.ReadOuterXml());

reader.Close();

Figure 12-8 shows the resulting page.

Figure 12-8. Transforming XML to HTML

Using the Xml Control

In some cases you might want to combine transformed HTML output with other content and web controls. In this case, you can use the Xml control. The Xml control displays the result of an XSL transformation in a discrete portion of a page.

C H A P T E R 1 2 X M L

453

For example, consider the previous XSLT example, which transformed DvdList.xml using DvdList.xsl. Using the Xml control, all you need is a single tag that sets the DocumentSource and TransformSource properties, as shown here:

<asp:Xml runat="server"

DocumentSource="DvdList.xml" TransformSource="DvdList.xsl" />

The best part of this example is that all you need to do is set the XML input and the XSL transform file. You don’t need to manually initiate the conversion.

Note You don’t need separate files to use the Xml control. Instead of using the DocumentSource property, you can assign an XmlDocument object to the Document property or assign a string containing the XML content to the DocumentContent property. Similarly, you can supply the XSLT information by assigning an XslTransform object to the Transform property. These techniques are useful if you need to supply XML and XSLT data programmatically (for example, if you extract it from a database record).

XML Data Binding

Now that you’ve learned how to read, write, and display XML by hand, it’s worth considering a shortcut that can save a good deal of code—the XmlDataSource control.

The XmlDataSource control works in a declarative way that’s analogous to the SqlDataSource and ObjectDataSource controls you learned about in Chapter 9. However, it has two key differences:

The XmlDataSource extracts information from an XML file, rather than a database or data access class. It provides other controls with an XmlDocument object for data binding.

XML content is hierarchical and can have an unlimited number of levels. By contrast, the SqlDataSource and ObjectDataSource return flat tables of data.

The XmlDataSource also provides a few features in common with the other data source controls, including caching and rich design support that shows the schema of your data in bound controls.

In the following sections, you’ll see how to use the XmlDataSource in simple and complex scenarios.

Nonhierarchical Binding

The simplest way to deal with the hierarchical nature of XML data is to ignore it. In other words, you can bind the XML data source directly to an ordinary grid control such as the GridView.

The first step is to define the XML data source and point it to the file that has the content you want to use:

<asp:XmlDataSource ID="sourceDVD" runat="server" DataFile="DvdList.xml" />

Now you can bind the GridView with automatically generated columns, in the same way you bind it to any other data source:

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="True" DataSourceID="sourceDVD">

454 C H A P T E R 1 2 X M L

Note Remember, you don’t need to use automatically generated columns. If you refresh the schema at design time, Visual Studio will read the DvdList.xml file, determine its structure, and define the corresponding GridView columns explicitly.

Now, when you run the page, the XmlDataSource will extract the data from the DvdList.xml file, provide it to the GridView as an XmlDocument object, and call DataBind(). Because the XmlDocument implements the IEnumerable interface, the GridView can walk through its structure in much the same way as it walks through a DataView. It traverses the XmlDocument.Nodes collection and gets all the attributes for each XmlNode.

Tip You can use the XmlDataSource programmatically. Call XmlDataSource.GetXmlDocument() to cause it to return the file’s content as an XmlDocument object.

However, this has a catch. As explained earlier, the XmlDocument.Nodes collection contains only the first level of nodes. Each of these nodes can contain nested nodes through its own XmlNode.Nodes collection. However, the IEnumerable implementation that the XmlDocument uses doesn’t take this into account. It walks over only the upper level of XmlNode objects, and as a result you’ll see only the top level of nodes, as shown in Figure 12-9.

Figure 12-9. Flattening XML with data binding

You can make this binding explicit by defining columns for each attribute:

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataSourceID="sourceDVD">

<Columns>

<asp:BoundField DataField="ID" HeaderText="ID" SortExpression="ID" /> <asp:BoundField DataField="Category" HeaderText="Category" SortExpression="Category" />

</Columns>

</asp:GridView>

C H A P T E R 1 2 X M L

455

In other words, if you don’t customize the XML data binding process, you can bind only to the top-level of nodes, and you can display text only from the attributes of that node. Furthermore, if there is more than one type of top-level node, the bound control uses the schema of the first node. In other words, if you have a document like this:

<DvdList>

<Retailer ID="..." Name="...">...</Retailer> <Retailer ID="..." Name="...">...</Retailer>

<DVD ID="..." Category="...">...</DVD> <DVD ID="..." Category="...">...</DVD> <DVD ID="..." Category="...">...</DVD>

</DvdList>

the GridView will inspect the first node and create an ID and Name column. It will then attempt to display ID and name information for each node. If no matching attribute is found (for example, the <DVD> specifies a name), then that value will be left blank. Similarly, the Category attribute won’t be used, unless you explicitly define it as a column.

All of this raises an obvious question—how do you display other information from deeper down in the XML document? You have a few options:

You can use XPath to filter out the important elements.

You can use an XSL transformation to flatten the XML into the structure you want.

You can nest one data control inside another (similar to the way that the master-child details grid was created in Chapter 10).

You can use a control that supports hierarchical data. The only ready-made .NET control that fits is the TreeView.

You’ll see all of these techniques in the following sections.

Using XPath

Ordinarily, when you bind an XmlNode, you display only attribute values. However, you can get the text from nested elements using XPath data binding expressions.

The most flexible way to do this is to use a template that defines XPath data binding expressions. XPath data binding expressions are similar to Eval() expressions, except instead of supplying the name of the field you want to display, you supply an XPath expression based on the current node.

For example, here’s an XPath expression that starts at the current node, looks for a nested node named Title, and gets associated element text:

<%# XPath("./Title")%>

Here’s an XPath expression that filters out the text of an ID attribute for the current node:

<%# XPath("./@ID")%>

Tip You can use the XPath data binding syntax with your own custom data objects, but it isn’t easy. The only requirement is that the data item must implement the IXPathNavigable interface.

456 C H A P T E R 1 2 X M L

Finally, here’s a GridView with a simple set of XPath expressions:

<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataSourceID="sourceDVD">

<Columns>

<asp:TemplateField HeaderText="DVD"> <ItemTemplate>

<b><%# XPath("./Title") %></b><br /> <%# XPath("./Director") %><br />

</ItemTemplate>

</asp:TemplateField>

</Columns>

</asp:GridView>

As with the Eval() method, you can use an optional second parameter with a format string:

<%# XPath("./Price", "{0:c}") %>

Figure 12-10 shows the result.

Figure 12-10. XML data binding with templates

Note Unfortunately, you need to use a template to gain the ability to write XPath data binding expressions. That limits the usefulness of other controls (such as drop-down lists) in XML data binding scenarios. Although you can bind them to attributes without any problem, you can’t bind them to show element content.

You can also use XPath to filter out the initial set of matches. For example, imagine you want to create a grid that shows a list of stars rather than a list of movies. To accomplish this, you need to use the XPath support that’s built into the XmlDataSource to prefilter the results.

C H A P T E R 1 2 X M L

457

To use XPath, you need to supply the XPath expression that selects the data you’re interested in by using the XmlDataSource.XPath property. This XPath expression extracts an XmlNodeList, which is then made available to the bound controls.

<asp:XmlDataSource ID="sourceDVD" runat="server" DataFile="DvdList.xml" XPath="/DvdList/DVD/Starring/Star" />

If that expression returns a list of nodes, and all the information you need to display is found in attributes, you don’t need to perform any extra steps. However, if the information is in element text, you need to create a template.

In this example, the template simply displays the text for each <Star> node:

<asp:GridView ID="GridView1" runat="server" DataSourceID="sourceDVD" AutoGenerateColumns="False">

<Columns>

<asp:TemplateField HeaderText="DVD"> <ItemTemplate>

<%# XPath(".") %><br /> </ItemTemplate>

</asp:TemplateField>

</Columns>

Figure 12-11 shows the result.

Figure 12-11. Using XPath to filter out results

You can create a simple record browser using the XmlDataSource.XPath property. Just let the user choose an ID from another control (such as a drop-down list), and then set the XPath property accordingly:

sourceDVD.XPath = "/DVDList/DVD[@ID=" + dropDownList1.SelectedValue + "]";

This works because data binding isn’t performed until the end of the page life cycle.