Wrox Home  
Search
Professional ASP.NET 3.5 : in C# and VB
by Bill Evjen, Scott Hanselman, Devin Rader
March 2008, Paperback


Excerpt from Professional ASP.NET 3.5: in C# and VB

Editing XML and XML Schema in Visual Studio 2008

by Scott Hanselman

If you start up Visual Studio 2008 and open the Books.xml, which is listing 10-1 from the code download for the book Professional ASP.NET 3.5: in C# and VB (Wrox, 2008, ISBN: 978-0-470-18757-9) file into the editor, you notice immediately that the Visual Studio editor provides syntax highlighting and formats the XML document as a nicely indented tree. If you start writing a new XML element anywhere, you don't have access to IntelliSense. Even though the http://example.books.com namespace is the default namespace, Visual Studio 2008 has no way to find the Books.xsd file (listing 10-3 from the code download for the book Professional ASP.NET 3.5: in C# and VB); it could be located anywhere. Remember that the namespace is not a URL. It's a URI — an identifier. Even if it were a URL it wouldn't be appropriate for the editor, or any program you write, to go out on the Web looking for a schema. You have to be explicit when associating XML Schema with instance documents.

Classes and methods are used to validate XML documents when you are working programmatically, but the Visual Studio editor needs a hint to find the Book.xsd schema. Assuming the Books.xsd file is in the same directory as Books.xml, you have three ways to inform the editor:

  • Open the Books.xsd schema in Visual Studio in another window while the Books.xml file is also open.
  • Include a schemaLocation attribute in the Books.xml file.
  • If you open at least one XML file with the schemaLocation attribute set, Visual Studio uses that schema for any other open XML files that don't include the attribute.
  • Add the Books.xsd schema to the list of schemas that Visual Studio knows about internally by adding it to the Schemas property in the document properties window of the Books.xml file. When schemas are added in this way, Visual Studio checks the document's namespace and determines if it already knows of a schema that matches.

The schemaLocation attribute is in a different namespace, so include the xmlns namespace attribute and your chosen prefix for the schema's location, as shown in Listing 10-4.

Listing 10-4: Updating the Books.xml file with a schemaLocation attribute

<?xml version='1.0'?>
<!-- This file is a part of a book store inventory database -->
<bookstore xmlns="http://example.books.com" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://example.books.com Books.xsd">
    <book genre="autobiography" publicationdate="1981" ISBN="1-861003-11-0">
        <title>The Autobiography of Benjamin Franklin</title>
        ...Rest of the XML document omitted for brevity...

The format for the schemaLocation attribute consists of pairs of strings separated by spaces where the first string in each pair is a namespace URI and the second string is the location of the schema. The location can be relative, as shown in Listing 10-4, or it can be an http:// URL or file:// location.

When the Books.xsd schema can be located for the Books.xml document, Visual Studio 2008's XML Editor becomes considerably more useful. Not only does the editor underline incorrect elements with blue squiggles, it also includes tooltips and IntelliSense for the entire document, as shown in Figure 1.

Figure 1
Figure 1

When the XML Schema file from Listing 10-3 is loaded into the Visual Studio Editor, the default view in Visual Studio 2008 for standard XSDs is now the XML Editor, rather than the Dataset Designer as in Visual Studio 2005. However, if you right-click on Books.xsd in the Solution Explorer and select Open With, you'll get a brief warning that the DataSet Designer might have modified your schema by removing non-dataset XML. Make sure your schema is backed up and select OK, and you'll get a redesigned Dataset Designer that presents the elements and complex types in a format that is familiar if you've edited database schemas before (see Figure 2). This Designer view is intended to manipulate DataSets expressed as schema, but it can be a useful visualizer for many XSDs. However, as soon as you use this visualizer to edit your XSD, your document will be turned into a Microsoft DataSet. Therefore the Dataset Designer in Visual Studio 2008 is no longer suitable as a general purpose visual XML Schema Editor.

Figure 2
Figure 2

A greatly enhanced XSD Designer will be released as an add-on soon after the release of Visual Studio but wasn't yet released at the time of this writing. This new XML Schema Editor will include a Schema Explorer toolbox window that will present a comprehensive tree-view of complex schemas in a much more scalable and appropriate way than can a more traditional ER-Diagram. For more details, see Figure 3 or go to http://blogs.msdn.com/xmlteam/archive/2007/08/27/announcing-ctp1-of-the-xml-schema-designer.aspx.

Figure 3
Figure 3

After you have created an XML Schema that correctly describes an XML document, you're ready to start programmatically manipulating XML. The System.Xml and System.Xml.Linq namespaces provide a number of ways to create, access, and query XML. XML Schemas provide valuable typing information for all XML consumers that are type aware.

XmlReader and XmlWriter

XmlReader offers a pull-style API over an XML document that is unique to the .NET Framework. It provides fast, forward-only, read-only access to XML documents. These documents may contain elements in multiple namespaces. XmlReader is actually an abstract class that other classes derive from to provide specific concrete instances like XmlTextReader and XmlNodeReader.

Things changed slightly with XmlReader between the .NET Framework 1.1 and 2.0, although nothing significant changed in the XmlReader and XmlWriter classes in .NET 3.5 as most of the new functionality was around LINQ. Since .NET 1.1, several convenient new methods have been added, and the way you create XmlReader has changed for the better. XmlReader has become a factory. The primary way for you to create an instance of an XmlReader is by using the Static/Shared Create method. Rather than creating concrete implementations of the XmlReader class, you create an instance of the XmlReaderSettings class and pass it to the Create method. You specify the features you want for your XmlReader object with the XmlReaderSettings class. For example, you might want a specialized XmlReader that checks the validity of an XML document with the IgnoreWhiteSpace and IgnoreComments properties pre-set. The Create method of the XmlReader class provides you with an instance of an XmlReader without requiring you to decide which implementation to use. You can also add features to existing XmlReaders by chaining instances of the XmlReader class with each other because the Create method of XmlReader takes another XmlReader as a parameter.

If you are accustomed to using the XmlDocument or DOM to write an entire XML fragment or document into memory, you will find using XmlReader to be a very different process. A good analogy is that XmlReader is to XmlDocument what the ADO ForwardOnly recordset is to the ADO Static recordset. Remember that the ADO Static recordset loads the entire results set into memory and holds it there. Certainly, you wouldn't use a Static recordset if you want to retrieve only a few values. The same basic rules apply to the XmlReader class. If you're going to run through the document only once, you don't want to hold it in memory; you want the access to be as fast as possible. XmlReader is the right decision in this case.

Listing 10-5 creates an XmlReader class instance and iterates forward through it, counting the number of books in the Books.xml document from Listing 10-1. The XmlReaderSettings object specifies the features that are required, rather than the actual kind of XmlReader to create. In this example, IgnoreWhitespace and IgnoreComments are set to True. The XmlReaderSettings object is created with these property settings and then passed to the Create method of XmlReader.

Listing 10-5: Processing XML with an XmlReader

VB

Imports System.IO
Imports System.Xml


Partial Class _Default
    Inherits System.Web.UI.Page


    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
           Handles Me.Load
        Dim bookcount As Integer = 0
        Dim settings As New XmlReaderSettings()


        settings.IgnoreWhitespace = True
        settings.IgnoreComments = True


        Dim booksFile As String = Server.MapPath("books.xml")
        Using reader As XmlReader = XmlReader.Create(booksFile, settings)
            While (reader.Read())
                If (reader.NodeType = XmlNodeType.Element _
                        And "book" = reader.LocalName) Then
                    bookcount += 1
                End If
            End While
        End Using
        Response.Write(String.Format("Found {0} books!", bookcount))
    End Sub
End Class

C#

using System;
using System.IO;
using System.Xml;


public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        int bookcount = 0;
        XmlReaderSettings settings = new XmlReaderSettings();


        settings.IgnoreWhitespace = true;
        settings.IgnoreComments = true;


        string booksFile = Server.MapPath("books.xml");
        using (XmlReader reader = XmlReader.Create(booksFile, settings))
        {
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element &&
                        "book" == reader.LocalName)
                {
                    bookcount++;
                }
            }
        }
        Response.Write(String.Format("Found {0} books!", bookcount));
    }
}

Notice the use of the XmlReader.Create method in Listing 10-5. You may be used to creating concrete implementations of an XmlReader, but if you try this technique, you should find it much more flexible because you can reuse the XmlReaderSettings objects in the creation of other instances of XmlReader. XmlReader implements IDisposable, so the Using keyword is correct in both VB and C#.

In Listing 10-5 the Books.xml file is in the same directory as this ASPX page, so a call to Server.MapPath gets the complete path to the XML file. The filename with full path is then passed into XmlReader.Create, along with the XmlReaderSettings instance from a few lines earlier.

The Read method continues to return true if the node was read successfully. It will return false when no more nodes are left to read. From the point of view of an XmlReader, everything is a node including white space, comments, attributes, elements, and end elements. If Listing 10-5 had simply spun through the while loop incrementing the bookcount variable each time reader.LocalName equaled book, the final value for bookcount would have been six. You would have counted both the beginning book tag and the ending book tag. Consequently, you have to be more explicit, and ensure that the if statement is modified to check not only the LocalName but also the NodeType.

The Reader.LocalName property contains the non–namespace qualified name of that node. The Reader.Name property is different and contains the fully qualified name of that node including namespace. The Reader.LocalName property is used in the example in Listing 10-5 for simplicity and ease. You'll hear more about namespaces a little later in the chapter.

Using XDocument Rather Than XmlReader

The System.Xml.Linq namespace introduces a new XDocument class that presents a much friendlier face than XmlDocument, while still allowing for interoperability with XmlReaders and XmlWriters. Listing 10-5q accomplishes the same thing as Listing 10-5 but uses XDocument instead. The XDocument is loaded just like an XmlDocument but the syntax for retrieving the elements we want is significantly different.

The syntax for this query is very clean, but slightly reversed from what you may be used to if you've used T-SQL. Rather than select...from, we're using the standard LINQ from...select syntax. We ask the booksXML XDocument for all its book descendants, and they are selected into the book range variable. The value of all the book title elements is then selected into the books variable.

VB takes the opportunity in Visual Studio 2008 to distinguish itself considerably from C# by including a number of bits of "syntactic sugar," which makes the experience of working with Visual Basic and XML more integrated. Notice the use of the Imports keyword to declare an XML namespace, as well as the use of "...<>" to indicate the method call to Descendants and ".<>" to call Elements. This extraordinary level of XML integration with the compiler really makes working with XML in VB a joy—and this is a C# lover speaking.

Listing 10-5q: Processing XML with a XDocument

VB

Imports System.IO
Imports System.Xml
Imports System.Linq
Imports System.Xml.Linq


Imports <xmlns:b="http://example.books.com">


Partial Class _Default
    Inherits System.Web.UI.Page


    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
           Handles Me.Load


        Dim booksXML = XDocument.Load(Server.MapPath("books.xml"))
        Dim books = From book In booksXML...<b:book> Select book.<b:title>.Value


        Response.Write(String.Format("Found {0} books!", books.Count()))
    End Sub
End Class

C#

using System;
using System.IO;
using System.Linq;
using System.Xml.Linq;


public partial class _Default : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        XDocument booksXML = XDocument.Load(Server.MapPath("books.xml"));        
        var books = from book in 
                    booksXML.Descendants("{http://example.books.com}book")
                    select book.Element("{http://example.books.com}title").Value;
        Response.Write(String.Format("Found {0} books!", books.Count()));
    }
}

In both the C# and VB examples, we take advantage of the implicit typing by not indicating the return type in the call to XDocument.Decendants. In VB we use Dim books and in C# we use var books. In this example, because we are using the from...select syntax to select our books from the booksXml object, the type of the variable books is System.Linq.Enumerable.SelectIterator, which is ultimately IEnumerable. The count method is added by LINQ as an extension method, allowing us to retrieve the number of books.

Notice also that the Books.xml document has a namespace of http://examples.books.com, so elements with this namespace are included in the query using the LINQ for XML format of {namespace}element.

This excerpt is from Chapter 10, "Working with XML and LINQ to XML," of the book Professional ASP.NET 3.5: in C# and VB (Wrox, Feb-2008, ISBN: 978-0-470-18757-9). Scott Hanselman works for Microsoft as a Senior Program Manager in the Developer Division, aiming to spread the good word about developing software, most often on the Microsoft stack. Before his latest role, he worked in eFinance for 6+ years, and before that he was a Principal Consultant at a Microsoft Partner for nearly 7 years. He was also involved in a few things like the MVP and RD programs and will speak about computers (and other passions) whenever someone will listen to him. He blogs at http://www.hanselman.com/and podcasts at http://www.hanselminutes.com and contributes to http://www.asp.net/, http://www.windowsclient.net/, and http://www.silverlight.net. Scott is one of the co-authors of the book, Professional ASP.NET 3.5: in C# and VB and the best-selling ASP.NET 2.0 book, Professional ASP.NET 2.0. Other related articles of interest by Scott and his co-authors Bill Evjen and Devin Rader include ASP.NET AJAX in ASP.NET 3.5 and Visual Studio 2008, ASP.NET 3.5 Windows-Based Authentication, Manipulating ASP.NET Pages and Server Controls with JavaScript, Connecting to Oracle or Access from ASP.NET 2.0, Using the ASP.NET 2.0 SQL Server Cache Dependency, and ASP.NET 2.0 FileUpload Server Control.