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

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

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

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

Nested Grids

Another option is to show nested elements by nesting one grid control inside another. This allows you to deal with much more complex XML structures.

The remarkable part is that ASP.NET provides support for this approach without requiring you to write any code. This is notable, especially because it does require code to create the nested master-details grid display demonstrated in Chapter 10.

The next example uses nested grids to create a list of movies, with a separate list of starring actors in each movie. To accomplish this, you begin by defining the outer grid. Using a template, you can display the title and director information:

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

<Columns>

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

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

...

Now, you need to define another GridView control inside the template of the first GridView. The trick is in the DataSource property, which you can set using a new XPathSelect() data binding statement, as shown here:

...

<asp:GridView id="GridView2" AutoGenerateColumns="False"

DataSource='<%# XPathSelect("./Starring/Star") %>' runat="server">

...

When you call XPathSelect(), you supply the XPath expression that retrieves the XmlNodeList based on a search starting at the current node. In this case, you need to drill down from the root <DvdList> element to the group of <Star> elements.

Once you’ve set the right data source, all you need to do is define a template in the second GridView that displays the appropriate information. In this case, you need only a single data binding expression to get the element text:

...

<Columns>

<asp:TemplateField>

<ItemTemplate>

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

</asp:TemplateField>

</Columns>

</asp:GridView>

</ItemTemplate>

</asp:TemplateField>

</Columns>

</asp:GridView>

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

459

Figure 12-12 shows the grid, with a little extra formatting added for good measure.

Figure 12-12. Showing XML with nested grids

Tip In this example, you might want to consider using the Repeater to show the actor names. That way, you have the flexibility to show the list in a more compact format, without using a table.

Hierarchical Binding with the TreeView

Some controls have the built-in smarts to show hierarchical data. In .NET, the principal example is the TreeView. When you bind the TreeView to an XmlDataSource, it uses the XmlDataSource.GetHierarchicalView() method and displays the full structure of the XML document (see Figure 12-13).

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

Figure 12-13. Automatically generated TreeView bindings

The TreeView’s default XML representation still needs a lot to be desired. It shows only the document structure (the element names), not the document content (the element text). It also ignores attributes. To improve this situation, you need to set the TreeView.AutoGenerateDataBindings property to false, and you then need to explicitly map different parts of the XML document to TreeView nodes.

<asp:TreeView ID="TreeView1" runat="server" DataSourceID="sourceDVD" AutoGenerateDataBindings="False">

...

</asp:TreeView>

To create a TreeView mapping, you need to add <TreeNodeDataBinding> elements to the <DataBinding> section. You must start with the root element and then add a binding for each level you want to show. You cannot skip any levels.

Note The approach you use to customize bindings with a TreeView is not completely finalized and may change in the final release of ASP.NET 2.0.

Each <TreeNodeDataBinding> must name the node it binds to (through the DataMember property), the text it should display (DataField), and the hidden value for the node (ValueField). Unfortunately, both DataField and ValueField are designed to bind to attributes. If you want to bind to element content, you can use an ugly hack and specify the #InnerText code. However, this shows all the inner text, including text inside other more deeply nested nodes.

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

461

The next example defines a basic set of nodes to show the movie title information:

<asp:TreeView ID="TreeView1" runat="server" DataSourceID="sourceDVD" AutoGenerateDataBindings="False">

<DataBindings>

<asp:TreeNodeBinding DataMember="DvdList" Text="Root" Value="Root" /> <asp:TreeNodeBinding DataMember="DVD" TextField="ID" /> <asp:TreeNodeBinding DataMember="Title" TextField="#InnerText" />

</DataBindings>

</asp:TreeView>

Figure 12-14 shows the result.

Figure 12-14. Binding to specific content

To get a more practical result with TreeView data binding, you need to use an XSL transform to create a more suitable structure, as described in the next section.

Tip To learn how to format the TreeView, including how to tweak gridlines and node pictures, refer to Chapter 16.

Using XSLT

The XmlDataSource has similar built-in support for XSL transformations. The difference is that you don’t use the stylesheet to convert the XML to HTML. Instead, you use it to convert the source XML document into an XML structure that’s easier to data bind. For example, you might generate an XML document with just the results you want and generate a flattened structure (with elements converted into attributes) for easier data binding.

To specify a stylesheet, you can set the XmlDataSource.TransformFile to point to a file with the XSL transform, or you can supply the stylesheet as a single long string using the XmlDataSource.Transform property. You can use both stylesheets and XPath expressions, but the stylesheet is always applied first.

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

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

One good reason to use the XSLT features of the XmlDataSource is to get your XML data ready for display in a hierarchical control such as the TreeView. For example, imagine you want to create a list of stars grouped by movie. You also want to put all the content into attributes so it’s easy to bind.

Here’s the final XML you’d like:

<Movies>

<DVD ID="1" Title="The Matrix"> <Star Name="Keanu Reeves" /> <Star Name="Laurence Fishburne" />

</DVD>

<DVD ID="2" Title="Forest Gump"> <Star Name="Tom Hanks" /> <Star Name="Robin Wright" />

</DVD>

...

</Movies>

You can transform the original XML into this markup using the following XSL stylesheet:

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

<xsl:output method="xml"/> <xsl:template match="/">

<!-- Rename the root element. --> <xsl:element name="Movies">

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

</xsl:template>

<xsl:template match="DVD">

<!-- Keep the DVD element with the same name. --> <xsl:element name="{name()}">

<!-- Keep the ID attribute. --> <xsl:attribute name="ID">

<xsl:value-of select="@ID"/> </xsl:attribute>

<!-- Put the nested <Title> text into an attribute. --> <xsl:attribute name="Title">

<xsl:value-of select="Title/text()"/> </xsl:attribute>

<xsl:apply-templates select="Starring" /> </xsl:element>

</xsl:template>

<xsl:template match="Starring"> <xsl:element name="Stars">

<!-- Put the nested <Star> text into an attribute. --> <xsl:attribute name="Name">

<xsl:value-of select="Star/text()"/> </xsl:attribute>

</xsl:element>

</xsl:template>

</xsl:stylesheet>

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

463

Now you can bind this to the TreeView and display it with this set of bindings:

<asp:TreeView ID="TreeView1" runat="server" DataSourceID="sourceDVD" AutoGenerateDataBindings="False">

<DataBindings>

<asp:TreeNodeBinding DataMember="Movies" Text="Movies" /> <asp:TreeNodeBinding DataMember="DVD" TextField="Title" /> <asp:TreeNodeBinding DataMember="Stars" TextField="Name" />

</DataBindings>

</asp:TreeView>

Binding to XML Content from Other Sources

So far, all the examples you’ve seen have bound to XML content in a file. This is the standard scenario for the XmlDataSource control, but it’s not your only possibility. The other option is to supply the XML as text through the XmlDataSource.Data property.

You can set the Data property at any point before the binding takes place. One convenient time is during the Page.Load event:

protected void Page_Load(object sender, EventArgs e)

{

string xmlContent;

// (Retrieve XML content from another location.) sourceDVD.Data = xmlContent;

}

Tip If you use this approach, you may find it’s still a good idea to set the XmlDataSource.DataFile property at design time in order for Visual Studio to load the schema information about your XML document and make it available to other controls. Just remember to remove this setting when you’re finished developing, as the DataFile property overrides the Data property if they are both set.

This allows you to read XML content from another source (such as a database) and still work with the bound data controls. However, it requires adding some custom code.

Even if you do use the XmlDataSource.Data property, XML data binding still isn’t nearly as flexible as the .NET XML classes you learned about earlier in this chapter. One of the key limitations is that the XML content needs to be loaded into memory all at once as a string object. If you’re dealing with large XML documents, or you just need to ensure the best possible scalability for your

web application, you might be able to reduce the overhead considerably by using the XmlReader instead, even though it will require much more code. Handling the XML parsing process yourself also gives you unlimited flexibility to rearrange and aggregate your data into a meaningful summary, which isn’t always easy using XSLT alone.

Note If you do use the XmlDataSource to display XML data from a file, make sure you use caching to reduce the number of times that the file needs to be opened with the CacheDuration, CacheDependency, and CachePolicy properties. If your file changes infrequently, you’ll be able to keep it in the cache indefinitely, which guarantees good performance. On the other hand, if you need to update the underlying XML document frequently, you’re likely to run into multiuser concurrency headaches, as discussed in Chapter 13.

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

Updating XML Through the XmlDataSource

Unlike the SqlDataSource and the ObjectDataSource, the XmlDataSource doesn’t support editable binding. You can confirm this fact with a simple test—just bind the XmlDataSource to GridView, and add a CommandField with edit buttons. When you try to commit the update, you’ll get an error informing you that the data source doesn’t support this feature.

However, the XmlDataSource does provide a Save() method. This method replaces the file specified in the DataFile property with the current XML content. Although you need to add code to call the Save() method, some developers have used this technique to provide editable XML data binding.

The basic technique is as follows: when the user commits a change in a control, your code retrieves the current XML content as an XmlDocument object by calling the XmlDataSource.GetXmlDocument() method. Then, your code finds the corresponding node and makes the change using the features of XmlDocument (as described earlier in this chapter). You can find and edit specific nodes, remove nodes, or add nodes. Finally, your code must call the XmlDataSource.Save() method to commit the change.

Although this approach works perfectly well, it’s not necessarily a great way to design a website. The XML manipulation code can become quite long, and you’re likely to run into concurrency headaches if two users make different changes to the same XmlDocument at once. If you need to change XML content, it’s almost always a better idea to implement the logic you need in a separate component, using the XML classes described earlier.

XML and ADO.NET

Now that you’ve taken an exhaustive look at general-purpose XML and .NET, it’s worth taking a look at a related topic—the XML support that’s built into ADO.NET.

ADO.NET supports XML through the disconnected DataSet and DataTable objects. Both have the built-in intelligence to convert their collection rows into an XML document. You might use this functionality for several reasons. For example, you might want to share data with another application on another platform. Or you might simply use the XML format to serialize to disk so you can retrieve it later. In this case, you still use the same methods, although the actual data format isn’t important.

Table 12-2 lists all the XML methods of the DataSet.

Table 12-2. DataSet Methods for Using XML

Method

Description

GetXml()

Retrieves the XML representation of the data in the DataSet as a single

 

string.

WriteXml()

Writes the contents of the DataSet to a file or a TextWriter, XmlWriter, or

 

Stream object. You can choose a write mode that determines if change

 

tracking information and schema information is also written to the file.

ReadXml()

Reads XML data from a file or a TextReader, XmlReader, or Stream object

 

and uses it to populate the DataSet.

GetXmlSchema()

Retrieves the XML schema for the DataSet XML as a single string. No data

 

is returned.

WriteXmlSchema()

Writes just the XML schema describing the structure of the DataSet to a

 

file or a TextWriter, XmlWriter, or Stream object. You can choose to include

 

the schema at the beginning of the document.

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

465

Method

Description

ReadXmlSchema()

Reads an XML schema from a file or a TextReader, XmlReader, or Stream

 

object and uses it to configure the structure of the DataSet.

InferXmlSchema()

Reads an XML document with DataSet contents from a file or a

 

TextReader, XmlReader, or Stream object and uses it to infer what structure

 

the DataSet should have. This is an alternate approach to using the

 

ReadXmlSchema() method, but it doesn’t guarantee that all the data type

 

information is preserved.

 

 

Note .NET 2.0 adds support for XML directly to the DataTable class. This means you can use the ReadXml(), WriteXml(), ReadXmlSchema(), and WriteXmlSchema() methods of the DataTable to read or write XML for a single table in a DataSet.

Converting the DataSet to XML

Using the XML methods of the DataSet is quite straightforward, as you’ll see in the next example. This example uses two GridView controls on a page. The first DataSet is filled directly from the Employees table of the Northwind database. (The code isn’t shown here because it’s similar to what you’ve seen in the previous chapters.) The second DataSet is filled using XML.

Here’s how it works: once the DataSet has been created, you can generate an XML schema file describing the structure of the DataSet and an XML file containing the contents of every row. The easiest approach is to use the WriteXmlSchema() and WriteXml() methods of the DataSet. These methods provide several overloads, including a version that lets you write data directly to a physical file. When you write the XML data, you can choose between several slightly different formats by specifying an XmlWriteMode. You can indicate that you want to save both the data and the schema in a single file (XmlWriteMode.WriteSchema), only the data (XmlWriteMode.IgnoreSchema), or the data with both the current and the original values (XmlWriteMode.DiffGram).

Here’s the code that you need to save a DataSet to an XML file:

string xmlFile = Server.MapPath("Employees.xml"); ds.WriteXml(xmlFile, XmlWriteMode.WriteSchema);

This code creates an Employees.xml file in the current folder.

Now you can perform the reverse step by creating a new DataSet object and filling it with the data contained in the XML file using the DataSetReadXml() method as follows:

DataSet dsXml = new DataSet("Northwind"); dsXml.ReadXml(xmlFile);

This completely rehydrates the DataSet, returning it to its original state.

If you want to see the structure of the generated Employees.xml file, you can open it in Internet Explorer, as shown in Figure 12-15. Notice how the first part contains the schema that describes the structure of the table (name, type, and size of the fields), followed by the data itself.

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

Figure 12-15. Examining the DataSet XML

The DataSet XML follows a predefined format with a few simple rules:

The root document element is the DataSet.DataSetName (for example, Northwind).

Each row in every table is contained in a separate element, using the name of the table. The example with one table means that there are multiple <Employees> elements.

Every field in the row is contained as a separate tag in the table row tag. The value of the field is stored as text inside the tag.

Unfortunately, the DataSet doesn’t make it possible for you to alter the overall structure. If you need to convert the DataSet to another form of XML, you need to manipulate it by using XSLT or by loading it into an XmlDocument object.

Accessing a DataSet As XML

Another option provided by the DataSet is the ability to access it through an XML interface. This allows you to perform XML-specific tasks (such as hunting for a tag or applying an XSL transformation) with the data you’ve extracted from a database. To do so, you create an XmlDataDocument

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

467

that wraps the DataSet. When you create the XmlDataDocument, you supply the DataSet you want as a parameter, as follows:

XmlDataDocument dataDocument = new XmlDataDocument(myDataSet) ;

Now you can look at the DataSet in two ways. Because the XmlDataDocument inherits from the XmlDocument class, it provides all the same properties and methods for examining nodes and modifying content. You can use this XML-based approach to deal with your data, or you can manipulate the DataSet through the XmlDataDocument.DataSet property. In either case, the two views are kept automatically synchronized—when you change the DataSet, the XML is updated immediately, and vice versa.

For example, consider the pubs database, which includes a table of authors. Using the XmlDataDocument, you could examine a list of authors as an XML document and then apply an XSL transformation with the help of the Xml web control. Here’s the complete code you’d need:

// Create the ADO.NET objects.

SqlConnection con = new SqlConnection(connectionString); string SQL = "SELECT * FROM authors WHERE city='Oakland'"; SqlCommand cmd = new SqlCommand(SQL, con);

SqlDataAdapter adapter = new SqlDataAdapter(cmd); DataSet ds = new DataSet("AuthorsDataSet");

//Retrieve the data. con.Open();

adapter.Fill(ds, "AuthorsTable"); con.Close();

//Create the XmlDataDocument that wraps this DataSet. XmlDataDocument dataDoc = new XmlDataDocument(ds) ;

//Display the XML data (with the help of an XSLT) in the XML web control. XmlControl.Document = dataDoc ;

XmlControl.TransformSource = "authors.xslt" ;

Here’s the XSL stylesheet that does the work of converting the XML data into ready-to-display HTML:

<?xml version="1.0" encoding="UTF-8" ?>

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

<h1>The Author List</h1>

<xsl:apply-templates select="AuthorsTable"/> <i>Created through XML and XSLT</i>

</xsl:template>

<xsl:template match="AuthorsTable">

<p><b>Name: </b><xsl:value-of select="au_lname"/>, <xsl:value-of select="au_fname"/><br/>

<b>Phone: </b> <xsl:value-of select="phone"/></p> </xsl:template>

</xsl:stylesheet>

Figure 12-16 shows the processed data in HTML form.