Dan Wahlin XML For ASP - NET Developers Kaleidoscope
Dan Wahlin XML For ASP - NET Developers Kaleidoscope
Dan Wahlin XML For ASP - NET Developers Kaleidoscope
NET Developers
Dan Wahlin
COVER DESIGNER
Aren Howell
PAGE LAYOUT
Susan Geiselman
Contents at a Glance
Introduction 1
1 XML and ASP.NET Development 3
2 XML for ASP.NET Basics 13
3 XPath, XPointer, and XLink 51
4 Understanding DTDs and XML Schemas 89
5 Using the XmlTextReader and XmlTextWriter Classes in ASP.NET 133
6 Programming the Document Object Model (DOM) with ASP.NET 175
7 Transforming XML with XSLT and ASP.NET 233
8 Leveraging ADO.NET’s XML Features Using ASP.NET 293
9 SQL Server 2000, XML, and ASP.NET 349
10 Working with ASP.NET, XML, SOAP, and Web Services 399
Index 451
Table of Contents
Introduction 1
Acknowledgments
I would like to thank my wife and children for their extreme patience as I spent evening after
evening and weekend after weekend working on this book. Heedy, I know you’re happy I
finally finished this book! I’d also like to thank my mom and dad (Elaine and Danny) for
bringing me up in such a pleasant environment and pushing me to succeed in life. I couldn’t
have asked for better parents. To Paul Allsing and Lin Thatcher, thanks for giving me my first
shot at working in the technology industry. Had it not been for your confidence in me, I
wouldn’t have had the opportunity to write this book.
I’d also like to thank Chris Lovett and Mark Fussell of Microsoft for answering so many ques-
tions for me over the past year. Without their help and samples, the book would not have
turned out as well as it did. Finally, I’d like to thank Neil Rowe of Sams for being such a fun
editor to work with. His patience and understanding when code had to be rewritten from build
to build made the process much more enjoyable.
Tell Us What You Think!
As the reader of this book, you are our most important critic and commentator. We value your
opinion and want to know what we’re doing right, what we could do better, what areas you’d
like to see us publish in, and any other words of wisdom you’re willing to pass our way.
As an Associate Publisher for Sams, I welcome your comments. You can fax, e-mail, or write
me directly to let me know what you did or didn’t like about this book—as well as what we
can do to make our books stronger.
Please note that I cannot help you with technical problems related to the topic of this book,
and that because of the high volume of mail I receive, I might not be able to reply to every
message.
When you write, please be sure to include this book’s title and author as well as your name and
phone or fax number. I will carefully review your comments and share them with the author
and editors who worked on the book.
Fax: 317-581-4770
Email: [email protected]
My goal was to make this book a useful reference for ASP.NET developers needing to know
the ins and outs of working with XML in their applications. I also wanted developers to see a
variety of ways that XML could be used in application development. Reading about a particu-
lar concept is not enough; a code sample often has more value than several pages of text. As a
result, you’ll find that each chapter contains a large number of samples to reinforce concepts
discussed throughout the chapter. Happy XMLing!
XML and ASP.NET CHAPTER
1
Development
IN THIS CHAPTER
• XML’s Ancestor—SGML 4
• X Is for eXtensible 7
Unless you’ve been vacationing for a few years on a secluded island, it would be hard to miss
all the hype surrounding XML. Newspapers, magazines, and Web sites throughout the world
portray XML as the solution to many of our existing technological problems. This global atten-
tion has pushed XML into the limelight and resulted in a barrage of vendor applications claim-
ing XML support. But is XML really as good as everyone seems to think it is? Although it’s
not going to bring world peace or decrease ozone levels, it is certainly a power-packed technol-
ogy that has a lot to offer after you learn how it works and when it can be applied.
You’re probably keenly aware that XML provides a platform-independent way of describing
data that can be used in many applications. We’ll get into plenty of details concerning this a
little later, but before doing that, it’s important to first answer a few questions.
To start with, where did XML come from and why is it needed in today’s technologically ori-
ented world? More importantly, how can you as an ASP.NET developer leverage its power?
This chapter discusses the answers to these questions and compares XML to another language
you’re already familiar with. The chapters that follow will provide you with example after
example of how XML can be used with ASP.NET to add useful functionality to your develop-
ment projects. Before going too much further, let’s go ahead and answer the question of where
XML came from.
XML’s Ancestor—SGML
XML is a relatively new language in the world of markup languages. You’re probably quite
familiar with XML’s cousin language named Hypertext Markup Language, or simply HTML.
Markup languages are typically used to provide metadata about data. In the case of XML and
HTML, this is done through special tags such as the <font> tag in HTML. SQL Server’s sys-
tem tables are another example of metadata. They provide information about the structure of
data within the database itself. XML serves this same purpose except that it describes text-
based data.
Whereas the World Wide Web Consortium’s (W3C) XML 1.0 recommendation has only been
around since February of 1998, XML’s parent language, Standard Generalized Markup
Language (SGML), has been around as a standard since 1986. SGML is a complex language
standard used extensively by many industries, including the technical-publication industry, to
mark up data. It uses a syntax defined by SGML known as a Document Type Definition (DTD)
to specify the structure of a given SGML document.
Although XML is much easier to work with, many of its features and rules were derived from
the SGML language. When the W3C first began formulating what is now the XML 1.0 recom-
mendation back in 1996, they borrowed many concepts from SGML, including the DTD. To
make XML easier to work with and more Web-oriented, many of SGML’s complexities were
XML and ASP.NET Development
5
CHAPTER 1
left out. If you’re interested in reading a comparison of XML and SGML, take a look at 1
http://www.w3.org/TR/NOTE-sgml-xml-971215.
DEVELOPMENT
Now that you have some background on where XML came from, let’s take a look at why we
XML AND
ASP.NET
need it.
Although some assumptions can be made about what some of the data elements are within the
file, many of the elements provide no way for you to know what they represent. For example,
does the field 06/28/2000 shown in the first line represent a date? If so, what type of date?
How about the 234954048 field in the third line? Is it a part number, a customer number, or
something totally unrelated? Listing 1.1 shows this same file converted into XML.
Each data item can easily be recognized and understood because all the data has been
described using XML markup tags. Using these tags can make it easier for humans and
computers to process data.
Although it’s true that computers normally take care of automating many data-intensive appli-
cations, programs based on files such as the flat file shown earlier have little flexibility to
XML and ASP.NET Development
7
CHAPTER 1
changes in the data’s structure. On the other hand, XML enables applications to access data 1
based on descriptive tags rather than by position. This presents a great opportunity to increase
DEVELOPMENT
an application’s flexibility toward changes in data structure.
XML AND
ASP.NET
XML also presents a great opportunity to be able to share data between applications and even
components within an application. By knowing an XML file’s structure in advance, a compo-
nent within an application can work with data contained in the XML file and perform different
tasks. In some cases, the data may represent information about a customer or business sce-
nario. In other cases, the XML file may simply represent object property values that an
ASP.NET component can use to make programming simpler and more efficient.
XML also benefits from the capability to determine in advance what structure the file must fol-
low through incorporating DTD and schema files. Defining this structure allows for companies
with differing systems to exchange data without worrying about what system the data was
originally stored in. As long as both companies know what the XML document’s structure will
be, they can exchange data using XML syntax. Applications based on XML, such as BizTalk
Server or SQL Server 2000, help to make this process even easier, and when combined with
the power of ASP.NET, very advanced applications can be created.
Finally, as previously mentioned, XML does not require advanced degrees in computer science
to use it. Looking at the XML code in Listing 1.1, you can see that no intimidating characters
or syntax are being used. In fact, the XML file could be read and understood by an individual
with no technical experience at all. After the syntax, structural, and programmatic rules are
learned, XML documents can be created and used both in simple and complex applications.
X Is for eXtensible
One important benefit associated with XML that hasn’t been mentioned yet is its capability to
be extended. XML’s extensibility allows you as a developer to choose how tags within XML
documents are named and structured. In HTML, you’re limited to using specific tags that work
only when used in a predefined way. For instance, to place an image in an HTML document,
you must use the <img> tag. Within this tag, you can place only certain attributes, such as the
src and vspace attributes. XML allows for tags and attributes to be created at will, depending
on how the application being designed needs to be structured. Therefore, if you’d rather
describe a path to an image using a tag named <imagePath>, you’re free to do so (as long as
you also include a closing </imagePath> tag!).
At first glance, this may appear to be a step toward total anarchy. After all, if everyone can
create XML documents using tags that don’t follow any specific naming convention, how can
anyone else possibly use the documents? The answer is that XML’s extensibility is held in
check through the use of DTD or schema documents that define what structure a given XML
document must follow to be valid. Therefore, if you want to create a document using custom
XML for ASP.NET Developers
8
tags with names such as <myTag> or <myData> to describe data, you can. Anyone wanting to
use your XML document can do so if you provide them with a definition of how the document
is structured and what tags are used to describe the data.
The key to understanding the value of XML’s extensible framework is realizing that XML
doesn’t specify how to present data to an end user or system. XML as a markup language is
concerned only with describing data that can be manipulated and presented using other lan-
guages. To make this more clear, let’s take a look at some of the differences between XML
and HTML.
What does 274943494 represent in the code? Is it a bank account number, a part number, a per-
sonal identification number, or none of the above? We have no way of knowing by looking at
the HTML document because HTML only displays information, it doesn’t describe it.
XML and ASP.NET Development
9
CHAPTER 1
By comparison, XML says nothing about how to display data. Its sole purpose is to describe 1
the data using tags, attributes, and other items. The data found within the preceding HTML
DEVELOPMENT
code snippet could be written the following way in an XML document:
XML AND
ASP.NET
<population>274943494</population>
Now it’s easy to see exactly what the data represents—data associated with a population. The
data can further be described using other tags:
<unitedStates>
<population>274943494</population>
</unitedStates>
Now we know that the data is a population number associated with the United States.
Closing Tags
HTML provides a lot of freedom in leaving tags open and even has some tags that never have
to be closed. For example, in HTML, some tags can be left open without causing problems in
the browser. Here’s an example of a few of them:
• <img>
• <hr>
• <p>
• <br>
• <meta>
• <li>
• <option>
Looking through these tags, you’ll notice that HTML doesn’t require a matching end tag
for them. For example, placing an image into a page by using code such as <img src=
”file.gif”></img> isn’t correct in the HTML language. Other tags that normally require
closing tags can sometimes get by without the closing the tag, too. In newer browsers, a
<body> tag listed without a closing </body> tag may not even pose a problem to a Web page.
Both Microsoft and Netscape’s browsers have moved toward being more forgiving of improper
HTML coding.
XML is not nearly as forgiving. Although we’ll talk more about this in Chapter 2, all XML
tags must have a matching closing tag (or use a short-cut syntax that you’ll see later).
Forgetting to close a tag will result in an error message being received when the document is
parsed. This rule benefits the language because XML documents that are exchanged will all
XML for ASP.NET Developers
10
follow the same structure, making it easier for applications to use the data in a reliable and
consistent manner.
Element Nesting
Although it’s not a recommended practice, HTML tags can be improperly nested and still
function properly. The following HTML code illustrates this:
<font><b>Text</font></b>
XML strictly prohibits improper nesting of tags. If a given tag starts before another tag in an
XML document, the tag that is declared first must end after the child tag, as shown next:
<street><name>Oak</name></street>
Quoted Attributes
HTML is very flexible in how attributes located within tags can be written. A browser will
treat the following two code segments the same:
<table border=”0” bgcolor=”#000000”>
<table border=0 bgcolor=#000000>
Failure to follow this rule will result in an error generated by the XML parser.
Case Sensitivity
XML also differs from HTML in how strictly case sensitivity is enforced. Although the
following code is acceptable in HTML:
<table></Table>
it is not acceptable in XML because the first tag name is declared with a small t and the
second with a capital T. XML requires that a tag’s starting case match its ending tag’s case.
Microsoft’s MSXML3 parser performs all the preceding checks and much more. However, it 1
was designed to work with “classic” ASP rather than ASP.NET. Although you can make calls
DEVELOPMENT
to MSXML3 from C# or VB.NET via Interop (discussed in more detail in Chapter 6,
XML AND
ASP.NET
“Programming the Document Object Model (DOM) with ASP.NET”), it is not recommended
that you use MSXML3 if you want to fully leverage all the XML features found in the .NET
platform.
For ASP.NET, Microsoft has built a new parser from the ground up that handles just about any
XML task imaginable. It is found within a .NET assembly named System.Xml. The chapters
that follow cover topics related to the System.Xml assembly and others involving well-formed
documents, valid documents, accessing documents using the Document Object Model (DOM)
or an XmlReader, and transforming documents using Extensible Stylesheet Language
Transformations (XSLT). Many examples and details about using the System.Xml assembly
are presented throughout the book.
presenting data to end users. Transformations between XML and HTML via XSLT will be
moved from the server to the client. Pages targeted for different devices can be served more
effectively using an XML-based repository that can be transformed to fit the appropriate device
using ASP.NET and XSLT. Doing this will provide the client with more speed because sorts,
filters, and other functions can be performed without the need for a round-trip back to the
server.
The rise of Web services will allow XML-based data published by companies around the world
to be consumed and placed within your ASP.NET applications. Credit cards can be processed
and user IDs validated by passing XML fragments to and from remote servers. As an ASP.NET
developer, you can leverage XML in many ways to provide needed services. Many of these are
discussed in the chapters that follow.
Summary
The XML language provides an extensible and powerful standard for describing data that can
be used within and shared between applications. Although it looks a lot like HTML, the two
languages have many differences. HTML serves the purpose of presenting data through an
application such as a browser, whereas XML describes data using markup tags, but does not
say how it should be presented.
The next chapter takes a more detailed look at XML’s rules and structure. Understanding the
basics of XML is necessary to create efficient ASP.NET/XML applications.
XML for ASP.NET Basics CHAPTER
2
IN THIS CHAPTER
• What’s in an XML Document? 14
• Well-Formed XML Documents 16
• Valid XML Documents 20
• The XML Declaration 21
• XML Elements 24
• XML Attributes 25
• XML Namespaces 27
• XML Processing Instructions 34
• XML Comments 34
• XML Entities 35
• CDATA Sections 40
• Dealing with Whitespace 41
• The Relationship of XML to XHTML 42
• Using ASP.NET Objects to Generate XML 44
XML for ASP.NET Developers
14
Now that you’re familiar with how XML came into existence and some of the ways it can be
used, it’s time to learn how XML documents are created. You’ll be happy to know that many of
the concepts employed in HTML and ASP.NET programming are applicable to XML docu-
ment creation as well. Much like HTML, XML documents use various elements and attributes
that provide the document with structure. However, elements and attributes aren’t used in
marking up the data for presentation in a browser. They’re used to describe the data and per-
form other functions, many of which are detailed in later chapters.
It’s important to note that XML by itself doesn’t provide much functionality other than the
capability to describe data. XML is only one language in a family of other languages and tech-
nologies. It’s only when XML is combined with other languages and technologies (such as the
.NET platform) that its power is realized in building applications.
Before you learn about the other supporting technologies that work together to empower XML,
you should take a good look at how XML is created and what rules are involved in this cre-
ative process. If you feel that you’re already an expert in creating XML documents, feel free to
skip ahead to the next chapter.
The list of topics shown at the beginning of the chapter provides an overview of what you can
expect to learn. Although this list may seem somewhat lengthy, rest assured that you will be
provided with plenty of samples and analogies to help you gain an in-depth understanding of
each topic. By the end of the chapter, you should be able to create XML documents by hand or
dynamically using intrinsic ASP.NET objects. Having said that, let’s get started!
simply a combination of different XML elements and data. Although some of the elements
contained in the document may be confusing to you (until you read this chapter, anyway), it’s
easy to see that the code contains no cryptic characters (although it could), adheres to a partic-
ular layout standard, is regular ASCII text, and looks a lot like the HTML markup language
that you’ve likely used before.
ASP.NET BASICS
4: <menuItem itemNumber=”1”>
5: <hyperLink>/default/default.aspx</hyperLink>
XML FOR
6: <name>Home</name>
7: </menuItem>
8: <menuItem itemNumber=”2”>
9: <hyperLink/>
10: <name>About Us</name>
11: <menuItem itemNumber=”2a”>
12: <hyperLink>/calendar/default.aspx</hyperLink>
13: <name>Events Calendar</name>
14: </menuItem>
15: <menuItem itemNumber=”2b”>
16: <hyperLink>/news/default.aspx</hyperLink>
17: <name>Press Releases</name>
18: </menuItem>
19: <menuItem itemNumber=”2c”>
20: <hyperLink>/contact/default.aspx</hyperLink>
21: <name>Contact Numbers</name>
22: </menuItem>
23: </menuItem>
24: <!-- End menuItem Listings-->
25: </root>
The XML document in Listing 2.1 is a very simple representation of a hierarchical menu struc-
ture that could exist on a company Web site. This simple menu (similar to the Start menu in
Windows) contains two parent menu items named Home and About Us. The About Us menu
item contains three children menu items (Events Calendar, Press Releases, and Contact
Numbers). You’ll see how this XML can be used in an ASP.NET application in Chapter 6,
“Programming the Document Object Model (DOM) with ASP.NET.”
Although the actual data contained in the document, such as Contact Numbers (line 21) or
Events Calendar (line 13) may not mean much on their own, by looking at the document you
XML for ASP.NET Developers
16
can see that the Contact Numbers data represents the name of a menu item. You’ll also notice
that data entries such as /contact/default.aspx are easily identified as being hyperlinks,
even to people not familiar with what a hyperlink may look like. Code such as
<hyperLink>/contact/default.aspx</hyperlink>
is a dead giveaway that the data within the tags must be related to a hyperlink in some manner.
Although the <hyperLink> tag could have been replaced with an <a> tag, as seen in HTML,
the data is described much better by using the word “hyperLink” don’t you think? The capabil-
ity to describe data within an XML document, which was discussed in Chapter 1, is referred to
as being “self-describing.”
The XML menu structure shown in Listing 2.1 also gives us insight into what an XML docu-
ment actually contains. Other than the data, an XML document contains a range of other items
including elements, attributes, processing instructions, comments, and text to name a few.
These items and others are discussed in the sections that follow. Before jumping into the differ-
ent items found in an XML document, however, let’s take a look at some basic XML rules.
Viewing this code in Internet Explorer 4 or 5 is not a problem, because simple coding mistakes
such as this non–well-formed document are forgiven.
As XML was developed, it was realized that well-formed documents were essential for XML
to be used consistently between applications. Imagine sending a document that was not well
formed to a friend who had an XML parser that blew up when documents were not well
formed! Your friend wouldn’t be able to open the document and use it in the manner that you
had intended.
Wait, you say. . .this type of thing happens all the time with HTML or Dynamic HTML
(DHTML) in different browsers today, so what’s the big deal? Because XML documents must
work consistently between parsers and applications, the creators of XML required strict con-
formance to a few basic XML principles. These principles include the following:
• An XML document may contain only one root element.
• All XML elements must have a closing tag.
XML for ASP.NET Basics
17
CHAPTER 2
ASP.NET BASICS
XML FOR
The Root Element
The XML document shown in Listing 2.1 makes it easy to visually identify the root element of
the document because it’s actually named “root.” It could have just as easily been named
“startingElement” if desired, because the actual name doesn’t matter. For XML parsers to have
a reference point, an XML document must contain only one root element per document.
Although the following code is acceptable:
<?xml version=”1.0”?>
<root>
<p>Hello World</p>
</root>
the next section of code is not acceptable, because it doesn’t have a single root element:
<?xml version=”1.0”?>
<root>
<p>Hello World</p>
</root>
<root>
<p>Hello World2</p>
</root>
If you think of XML documents in terms of a tree, the root element is the root of the tree, and
all other child elements are the branches and leaves of the tree.
To show the differences between HTML and XML, try running the following XML document
in Internet Explorer 5+ and see what happens. To run it, enter the following code into Notepad
and save the file as test.xml. Then open the file in Internet Explorer 5+ using the File, Open
menu:
<?xml version=”1.0”?>
<root>
<p>Hello World
</root>
After the document opens, you’ll receive an error stating something like End tag ‘root’
does not match the start tag ‘p’. Line 4, Position 3. This error occurs because the
XML parser can’t find the ending </p> tag. To prove this, run the following code (which closes
the <p> tag) in Internet Explorer 5+ and you’ll see that the document loads fine:
<?xml version=”1.0”?>
<root>
<p>Hello World</p>
</root>
NOTE
It’s important to note that the <p> tag as shown in the preceding XML document
does not serve the same function as if it were used in an HTML document. Its use in
the XML document is simply to describe the data Hello World. When you open the
text.xml document in Internet Explorer 5, you’ll see that the <p> tag is actually visible
and doesn’t add any type of formatting to Hello World, as would be expected in
HTML. Remember that XML doesn’t specify how to format or display data (without
using other technologies such as XSLT). It only organizes and describes the data. The
<p> tag was used to show how you must close tags in XML, not because it has any
special function or purpose, as it does in HTML.
XML for ASP.NET Basics
19
CHAPTER 2
In cases where an XML tag contains no data or has only attributes (which you’ll learn more
about later in this chapter), XML provides us with a handy shortcut:
<menuItem itemNumber=”1”/>
Although the shortcut method may seem a little strange at first, after you’re comfortable using
it, you’ll find that it’s great for saving time and space when you need to include an element
that has only attributes (such as itemNumber in the preceding example) or that contains no 2
data.
ASP.NET BASICS
XML FOR
At this point, you may be wondering why this section is titled “<br> Versus <br/>.” Although
the <br> tag doesn’t actually mean “insert a line break” if used as an element within XML,
you’ll see in a later chapter that it can be used with related XML technologies, such as XSLT
or XHTML, to insert a line break. When used in this context, tags such as <br> and <img>
must be closed to avoid errors being raised. Therefore, <br> becomes <br/> and <img
src=”someImage.gif”> becomes <img src=”someImage.gif”/>. This may be a little confus-
ing because you haven’t learned anything about XSLT yet. If you take away nothing more
from this section than a knowledge that all tags must be closed in XML and its related tech-
nologies, then you know all you need to know for now. Future chapters build on this.
Nesting No-No’s
By now you certainly have no doubt that XML tags must be closed for an XML document to
be parsed correctly. Another “gotcha” must be followed as well. XML tags cannot be nested
like HTML tags can.
In HTML it’s possible, although not recommended, to code the following:
<font><b>Hello World</font></b>
This type of inappropriate nesting within an XML document is strictly prohibited and will
cause the parser to generate an error. To make this work with XML, you would need to fix the
nesting:
<font><b>Hello World</b></font>
To get away from HTML tags you already know (because <font> does nothing except describe
data in XML), here’s another improper nesting example that follows the same pattern as shown
previously:
<menuItem><name>About Us</menuItem></name>
XML for ASP.NET Developers
20
The following is the proper way to code this section, assuming you want to list more than one
element on a line:
<menuItem><name>About Us</name></menuItem>
Because you have yet to see an XML document with more than one element on a line, you
may be wondering if this is okay. The answer is a resounding “Yes!” The documents you’ve
seen thus far have been indented and spaced simply to make them easier to read. They do not
have to be created this way to work, but good luck trying to read them in the future without
spacing and indentation!
Other Rules
By now you’re probably sick of learning the rules and want to move on to something new.
Don’t worry—we’re almost done. Two more important points need to be mentioned concerning
case sensitivity and attribute quoting.
Unlike HTML, in which <font> is the same as <FONT> is the same as <Font>, XML is case
sensitive. All beginning and ending tags must be the same case. To continue using our menu
metaphor, <name>About Us</Name> will cause the parser to generate an error saying, End tag
‘Name’ does not match the start tag ‘name’. Determining what case to use is completely
up to you. Because the parser normally tells you what the problem is, this is an easy problem
to resolve, assuming that you pick a naming convention and stick to it.
One final rule exists that may or may not be more difficult to remember, depending on how
you code your HTML files. All XML attributes must be quoted for the parser to be able to
parse the document. This means that the following HTML code would not be acceptable if it
were written in an XML document:
<body bgcolor=#ffffff>
Because of the recent XHTML recommendation (covered later in this chapter) and because
you will be working with XML more in the near future, it is highly recommended that you get
into the habit of quoting all your HTML attributes. It will make the transition to XML and its
related technologies much easier.
that allow for the inclusion of basically any element. Going back to Listing 2.1, if you wanted
to include an <image> element as a child of all <menuItem> tags, you could do so without caus-
ing any problems in the XML document.
The freedom to add any desired element to an XML document probably sounds like a pretty
good feature, and it is in certain circumstances. However, imagine creating an application that
manipulates an XML document received from a particular supplier. This application is pro-
grammed to work with a specific XML document that is assumed to contain a consistent set of
elements and attributes. What happens if the supplier starts adding in elements that your appli-
cation isn’t expecting? The supplier’s “freedom” to add in new elements may cause your appli- 2
cation to malfunction, crash, or simply report incorrect data.
ASP.NET BASICS
To prevent this exact scenario from occurring, XML documents can be checked or validated
XML FOR
against another document to make sure that the structure of the XML document is valid. This
means that you can have an XML document that follows all the XML rules (and is thus well
formed) but is invalid. How can this be possible? Enter the world of Document Type
Definitions (DTDs) and XML schemas.
To allow for validation of XML documents, a type of “metadata” document can be created to
define what elements, attributes, and other items can be contained within the XML document.
By defining the structure of the XML document, the problems described in Chapter 1 with the
imaginary supplier can be prevented because the application can validate the document before
performing any calculations or transformations.
Because DTDs and XML schemas cannot be summarized in just a few paragraphs, you’ll learn
about them in greater detail in Chapter 4, “Understanding DTDs and XML Schemas.” Now it’s
time to cover the specifics on items used to construct an XML document.
XML declarations function in a manner similar to ASP.NET directives. You already saw your
first XML declaration back in Listing 2.1 even though you didn’t realize it at the time. The
XML declaration shown there was
<?xml version=”1.0” encoding=”UTF-8” standalone=”yes”?>
Before expounding on the different encoding types, it’s worth mentioning how computers store
text. A computer doesn’t actually store text as text, which may sound a little confusing. Rather,
it converts the text into a number or group of numbers that the computer recognizes as repre-
senting the text. Therefore, one particular computer may store a numerical representation of
the text differently than the same text on another computer.
To avoid problems that could potentially arise from different numeric representations of text on
computers, XML documents can specify how the document text is encoded. By doing this, a
computer receiving an XML document knows what encoding type to work with and can make
the appropriate transformation to read the document. 2
ASP.NET BASICS
This transformation process works because of character encodings developed by the Unicode
Consortium (http://www.unicode.org). To account for some older systems not being able to
XML FOR
read Unicode, the Unicode Transformation Formats (UTF) were developed. XML documents
default to the UTF-8 character encoding that uses single-byte encoding and Character Entities.
UTF-16 is a two-byte encoding standard that is needed to accommodate a large number of
characters such as those found in the Japanese or Chinese languages. Several other encoding
standards, including ISO-8859-1 and Shift_JIS, map to different encoding types.
Entire books have been written about Unicode and bit-shifting techniques, so this subject will
be covered only from a high-level perspective. The important point to take away from this
discussion is that many encoding types are available. But, the XML 1.0 Recommendation
specifies that all XML parsers must support the UTF-8 and UTF-16 encoding types. Sticking
to one of these types should help prevent problems that arise from exchanging XML docu-
ments between differing computer systems. An excellent article that gives an in-depth
look into the subject of XML encoding was written by Microsoft. You can find it at
http://msdn.microsoft.com/xml/articles/xmlencodings.asp.
XML Elements
The <a>, <hr>, <body>, and <div> tags found in the HTML language are all examples of
HTML elements. Each has a special meaning and function that different browsers understand.
Your familiarity with HTML makes understanding XML elements very straightforward and
easy, especially when you consider that an XML element is simply a container, like many of
the HTML elements you’re used to working with.
Listing 2.2 shows a few XML elements that you’ve seen before.
The <menuItem> element shown in Listing 2.2 is a parent to the <hyperLink> and <name>
elements. As described earlier in the chapter, each element tag must be closed for an XML
document to be considered well formed.
An XML element isn’t defined as only the tag itself. It includes all other subelements or con-
tent between the element’s starting and ending tags. This content is referred to as the “Content
Model” of the element. This means that the <menuItem> element shown in Listing 2.2 is
defined as containing the starting and ending <menuItem> tags as well as the <hyperlink> and
<name> tags.
The W3C XML 1.0 Recommendation allows document authors to name XML elements as they
would like. However, the elements must start with a character from A–Z or a–z or may start
with the ”_” character. Therefore, the following are all legal elements as defined by the W3C:
<bike>
<color>red</color>
<Type>adult bike</Type>
<STYLE1>Woman’s</STYLE1>
<_price>100.99</_price>
</bike>
Failure to follow the naming rules will result in an error similar to A name was started with
an invalid character.
XML Attributes
To continue with our HTML/XML comparisons, an attribute in XML is very similar to an
HTML attribute: the attribute helps describe an element. In HTML you can change the color
attribute to the <font> element:
<font color=”#02027a”>Some Text</font>
2
In XML you can follow the same guidelines:
ASP.NET BASICS
XML FOR
<menuItem itemNumber=”1”>Some Text</menuItem>
In this XML example, itemNumber is an attribute of the <menuItem> element. You’ll notice that
itemNumber’s value is quoted. Whereas in HTML you can get by without quoting attribute val-
ues, the XML rules require that all attribute values are quoted. Failure to follow this rule will
result in an error being raised by the parser.
The strict naming conventions applied to naming elements also apply to naming attributes in
XML. Attribute names must start with a character from A–Z or a–z, with the exception of the
“_” character. Failure to follow the naming rules will result in an error similar to A name was
started with an invalid character.
When to use attributes in an XML document is completely up to you. Some XML program-
mers use attributes rather than elements because they can take up less space and are quicker to
add to an XML document. For example, Listing 2.3 shows a recordset obtained from a data-
base converted to XML using only XML elements, whereas Listing 2.4 shows the same con-
version but with attributes used where possible.
As you can see from the code in Listing 2.4, using attributes to describe data can result in
smaller file sizes and may be considered by some to be easier to read. Whether attributes are
used is completely up to you. A good general rule to follow is that if data describes an element
in XML, it may be a good candidate for an attribute (although it doesn’t have to be). For exam-
ple, if you have an element named house and want to describe its color, you may want to
make color an attribute. Thinking in terms of object-oriented programming, an attribute is
similar to an object property.
If you realize that you’ve defined an attribute in your XML document that may actually need
to contain child elements of its own, the attribute should be changed to an element because
attributes cannot contain child elements. For example, the following XML document contains
an attribute named fname:
<?xml version=”1.0”?>
<root>
<name fname=”Dan” lname=”Wahlin” title=”Mr.”/>
</root>
What if the fname attribute actually needs to describe the birth name and nickname of an indi-
vidual? You could always delimit the two values with a pipe character, but this would defeat
the purpose of XML. Instead of doing this, you would want to break fname out as a child
element of the name element:
<?xml version=”1.0”?>
<root>
<name lname=”Wahlin” title=”Mr.”>
<fname>
<birthName>Daniel</birthName>
<nickName>Dan</nickName>
</fname>
</name>
</root>
<?xml version=”1.0”?>
<root>
<name lname=”Wahlin” title=”Mr.”>
<fname birthName=”Daniel” nickName=”Dan”/>
</name>
</root>
XML Namespaces
Namespaces are an important part of the XML language, especially because developers can cre-
ate their own element tag names in an XML document. A namespace allows tags with the same 2
name to be distinguished based on who created the tags and what the tags are meant to describe.
ASP.NET BASICS
XML FOR
One of the easiest ways to see why namespaces are needed in XML is to look at a more
abstract example. If I asked you where the phone number 669-5835 called to, what would be
your response? More than likely, you would say something like, “I have no idea because you
didn’t even supply an area code!” Now, what if I asked you where the phone number (480)
669-5835 called to? Although you might not know the individual residence or business this
number called, with a little research you would be able to learn that this calls a number located
somewhere in the Phoenix, Arizona metropolitan area. The area code helped determine who, or
in this case where, the number was associated.
Now that you’ve seen a more abstract example, let’s take a look at another that ties into XML
more directly. Part of XML’s inherent power lies in its capability to describe data through
creating custom tags. This can present problems because of name collisions, as the following
scenario illustrates.
Company A sends Company B an XML document containing data about A’s customers.
Company B combines its own XML document with Company A’s document and updates a
database with the combined information. Both documents contain an element named
customerID. Company A’s customerID element contains a unique customer identifier gener-
ated by its computer systems. This number can be stored “as is” in the database with no
manipulation. Company B’s customerID element contains the customer’s Social Security num-
ber, including dashes. Before inserting this value into the database, all dashes must be stripped
out. Listing 2.5 shows Company A’s XML document and Listing 2.6 shows Company B’s
XML document.
Reading through this sample scenario, you can see how combining Company A’s and Company
B’s XML documents may create a problem when the database needs to be updated. Because
both companies use the customerID element in a different way for describing their data, and
because Company B has a business rule stating that customer Social Security numbers must be
stored without dashes in the database, it will be very difficult to distinguish from which com-
pany the customerID element came. This will make it difficult to know when to apply
Company B’s business rule.
To complicate this more, what if Company A’s name element was actually describing where a
particular part was manufactured, whereas Company B’s name element was the actual customer
name? The resulting ambiguity could make document exchange between the companies diffi-
cult and could complicate application development.
To simplify this process and allow elements of an XML document to be uniquely identified
and associated with the appropriate creator, the W3C recommended XML namespaces. As you
will see, namespaces help to eliminate the guesswork that occurs when different companies, or
XML for ASP.NET Basics
29
CHAPTER 2
even different departments within the same company, use element or attribute names that are
the same but have different meanings.
Namespace Structure
Namespaces are easy to implement in an XML document after their structure is known and
understood. Before discussing namespaces in more detail, it’s important to know that all items
within an XML document are by default part of a namespace. This is because the reserved
keyword xml is associated with its own native namespace.
So that you gain a good understanding of namespace structure and types, let’s take a look at 2
code samples involving the following namespaces, which comply with the W3C’s January
ASP.NET BASICS
1999 Namespace Recommendation (http://www.w3.org/TR/1999/REC-xml-names-
XML FOR
19990114):
• Default Namespaces
• Qualified Namespaces
Default Namespaces
The following code shows a default namespace being applied to the <root> element:
<root xmlns=”http://www.anyURL.com”>
<menuItem itemNumber=”1”>
<hyperLink>/news/default.aspx</hyperLink>
<name>Press Releases</name>
</menuItem>
</root>
The preceding default namespace starts by using the keyword xmlns, which is shorthand for
“XML Namespace.” Because it is declared in the root of the XML document, it applies to all
content contained between the beginning and ending <root> tags. We can say that its scope is
“global” to the XML document because it applies to everything from the root down (or the
root up, if you think in terms of a tree structure).
The xmlns keyword is set equal to a Universal Resource Identifier, or URI, which in this
case is “http://www.anyURL.com”. The URI can be any unique identifier and does not
have to be a hyperlink. I could have just as easily specified xmlns=”myBusiness” if I had
wanted to. However, this wouldn’t be very efficient or unique because someone else could
use “myBusiness” as the URI for a namespace in their XML document. The best bet when
deciding on URI identifiers is to use your Web site URL followed by a particular path
(http://www.anyURL.com/myNamespace); it is unique and therefore unlikely that someone
else will use that same URL, especially because they have no ownership of it.
XML for ASP.NET Developers
30
The URL does not actually have to point to a real file. This is a difficult concept to grasp at
first. After all, why list a URL for the namespace URI if it doesn’t actually point to a file? To
answer this, realize that the URI is simply a unique identifier and that URLs work well in this
situation because they are unique to start with.
In the previous example, the root element contains the following definitions:
URI = “http://www.anyURL.com”
Local name = “root”
The <menuItem>, <hyperLink>, and <name> elements are associated with the following defini-
tions, respectively:
URI = “http://www.anyURL.com”
Local name = “menuItem”
URI = “http://www.anyURL.com”
Local name = “hyperLink”
URI = “http://www.anyURL.com”
Local name = “name”
You can see that the default namespace’s ”scope” is everything within the XML document in
this example because all elements inherited the URI of “http://www.anyURL.com”. From the
preceding definitions, you can also see that each element’s name is referred to as its Local
name.
To better understand the scope of default namespaces, let’s run through a simple example
involving a variable in ASP.NET. Let’s assume that you have an ASP.NET page with a variable
named menuItem declared at the top of the page. This variable is assigned a value of “Contact
Us”. The variable is not declared within a function, so it has a global scope as far as the page is
concerned. Because of the global nature of the variable, we can access its value from anywhere
in the page.
Now let’s apply this same ASP.NET variable example to the default namespace example shown
next:
<root xmlns=”http://www.anyURL.com”>
<menuItem itemNumber=”1”>
<hyperLink>/news/default.aspx</hyperLink>
<name>Press Releases</name>
</menuItem>
</root>
Because the default namespace declaration is “declared” at the root, all content within the
XML document will inherit this default namespace. This falls in line with the global menuItem
variable described in the ASP.NET example. Let’s see what happens if a default namespace is
declared in the menuItem tag, as shown next:
XML for ASP.NET Basics
31
CHAPTER 2
<root>
<menuItem itemNumber=”1” xmlns=”http://www.anyURL.com”>
<hyperLink>/news/default.aspx</hyperLink>
<name>Press Releases</name>
</menuItem>
</root>
The default namespace will apply to everything contained within the <menuItem> beginning
and ending tags. This says that the default namespace for the <hyperlink> and <name> tags is
now associated with the declared URI “http://www.anyURL.com” The root tag will not be a
part of this namespace because it isn’t within the <menuItem> beginning and ending tags. 2
ASP.NET BASICS
This local scope (local to the <menuItem> tag) is similar to moving our menuItem variable dec-
XML FOR
laration in the previous ASP.NET example into a function. Moving it within the function
makes the variable’s scope local to the function. Anything outside of the function cannot see or
access the variable unless a call is made to the function and the variable’s value is returned. A
similar process is occurring in the previous XML code. If we treat the <menuItem> tag like our
ASP.NET function, the same type of scope applies with respect to the default namespace:
xmlns=”http://www.anyURL.com”.
Qualified Namespaces
Although default namespaces are certainly useful when an element and all contents within it
share the same namespace, situations may arise when some of the contents within an element
come from a different namespace. Listing 2.7 shows an example.
From the preceding example, you can see that the <menuItem> and <image> elements are asso-
ciated with the “http://www.myCompany.com” default namespace. As you learned earlier, the
scope of this namespace normally includes everything that is contained within the <menuItem>
beginning and ending tags (remember the ASP.NET function comparison). In this example,
however, the <hyperlink> and <name> subelements belong to the “http://www.another
Company.com” default namespace. Because each of these subelements has its own default
XML for ASP.NET Developers
32
namespace defined, the namespace declared for the <menuItem> element that normally trickles
down is overridden.
Although the code shown in Listing 2.7 is one potential way of declaring the two namespaces,
what would happen if 10 (or 1000, for that matter) other elements were associated with the
“http://www.anotherCompany.com” namespace? If a change is made to the namespace URI,
implementing the change requires that each of the 10 elements are updated. As an ASP.NET
developer, this should raise some red flags.
What if a specific namespace needs to be applied to the itemNumber attribute only? Default
namespaces do not allow for this because they apply to all content within the element in which
they are declared.
To combat these problems, the qualified namespace was developed. A qualified namespace
allows for namespaces to be specifically applied to individual elements (and other XML items
such as attributes) rather than having the namespace trickle down as you’ve seen with default
namespaces. To make this more clear, Listing 2.8 shows an example that uses a qualified
namespace.
xmlns:another=”http://www.anotherCompany.com”>
<menuItem itemNumber=”1”>
<another:hyperLink>/news/default.aspx</another:hyperLink>
<another:name>Press Releases</another:name>
<image>picture.jpg</image>
</menuItem>
</root>
ASP.NET BASICS
done so with the following code segment:
XML FOR
<menuItem another:itemNumber=”1”>
Keep in mind that because attributes are always associated with specific elements, it’s not nor-
mally necessary to associate them with a namespace, because the namespace association can
be inferred from the element that they are describing.
TIP
As you read through the previous namespace examples, you may have wondered
how to clear a namespace after it was declared. For example, if a <root> element
declared a default namespace, how can that namespace be cleared if you don’t want
it to apply to a particular subelement? Going back to our ASP.NET comparisons,
namespaces can be cleared in the same manner as string variables declared in an
ASP.NET page—by setting the variable equal to empty quotes: variableName = “”. To
apply this same principle to clearing a default namespace, simply set the namespace
URI equal to empty quotes: <menuItem xmlns=””>.
Referring back to the problem that arose when XML documents from Company A and
Company B were merged, the customerID elements from each company could easily be identi-
fied correctly by prefixing them with a qualified namespace or by defining a default namespace
for each XML document. The following example shows how Company B’s document could be
rewritten using a qualified namespace so that naming conflicts associated with the customerID
element are eliminated:
<?xml version=”1.0”?>
<recordset xmlns:companyB=”http://www.companyb.com/customers/namespace”>
<row>
<companyB:customerID>528-11-1234</companyB:customerID>
<companyB:name>Dan Wahlin</companyB:name>
XML for ASP.NET Developers
34
Namespaces are not always needed in an XML document, but in situations like the preceding
one shown with Company A and Company B, many problems can be alleviated by using them.
PIs can also be used when you want a particular action to be taken by one parser but ignored
by another. For example, assume that a parser looks for a PI that tells it to copy the current
XML file to a network folder and rename it. Assuming a parser is capable of performing this
functionality, the PI to accomplish this task may look like the following:
<?copyFile path=”\\npath\folderName”?>
Although the exact instruction to perform this task would of course be dependent on the parser,
you can see that all PIs start with the <? characters and end with the ?> characters. PIs can be
very useful when multiple parsers will parse a given document. Processing instructions allow
each parser (RTF, CDF, XML, and so on) to perform specific functions without causing prob-
lems in other parsers.
XML Comments
XML comments are the same as HTML comments. Although I personally prefer commenting
single lines of code using the single apostrophe (‘) as in Visual Basic or the // notation found
in C#, XML comments are just as easy to use when commenting out large sections of code.
Back in Listing 2.1, you saw the use of XML comments:
<!-- start menuItem Listings -->
Although this was a very simple example of adding helpful comments to an XML document,
comments can also be used to comment entire sections out of an XML document, as shown in
Listing 2.9.
XML for ASP.NET Basics
35
CHAPTER 2
ASP.NET BASICS
11: </menuItem>
12: <menuItem itemNumber=”2b”>
XML FOR
13: <hyperLink>/news/default.aspx</hyperLink>
14: <name>Press Releases</name>
15: </menuItem>
16: <menuItem itemNumber=”2c”>
17: <hyperLink>/contact/default.aspx</hyperLink>
18: <name>Contact Numbers</name>
19: </menuItem>
20: </menuItem>
21: -->
22: </root>
Line 4 of Listing 2.9 starts the comment off by using the <!-- characters. This lets the parser
know that a comment is starting. The comment container continues until line 21, where the -->
characters signal to the parser that the comment has ended. Any XML content found between
the comment tags will be ignored by the parser.
WARNING
If any data within a commented section of code contains the -- characters, the parser
will throw an error saying, Incorrect syntax was used in a comment. An entity
should be used to represent these characters if they exist within the data found in an
XML document. Entities are discussed in the next section.
XML Entities
Back in the old days (you know. . .1994 or so) when I was first learning HTML, I had a hard
time remembering what characters needed to be put into an HTML document when I wanted
several spaces to show up in a browser. Having had many years of practice, my brain has
finally remembered (hopefully permanently) that the HTML character entity represents
XML for ASP.NET Developers
36
a space to the browser. As you are aware, HTML allows for a variety of characters to be
inserted into a Web page. For example, the HTML entity © represents the © character,
whereas the HTML entity ® represents the ® character. XML leverages entities in a simi-
lar manner as HTML.
XML uses the < and > characters to mark up an element, which can cause problems when these
characters exist in the data. To see this problem in action, try loading the following XML docu-
ment in Internet Explorer 5 or later:
<?xml version=”1.0”?>
<root>
<calculation>1 < 2</calculation>
</root>
You’ll see that the parser generates an error saying Whitespace is not allowed at this
location. At first glance, this error doesn’t seem to relate to the insertion of the < character
into the data. However, if you remember the discussion on XML element naming, you will
recall that element names cannot start with a space. The error is being generated because the
parser thinks that a new element is being started and the element name starts with a space.
Situations such as these involve Standard Entities.
Standard Entities
How can the preceding problem be solved? The answer is to take advantage of Standard
Entities defined in XML. These entities have a similar structure to the ones found in HTML.
Take a look at Table 2.1 to see the characters and their entity references.
Using the Standard Entity reference for the < character, we can fix the error that occurred when
the previous XML document was loaded:
<?xml version=”1.0”?>
<root>
<calculation>1 < 2</calculation>
</root>
XML for ASP.NET Basics
37
CHAPTER 2
Although < shows in the XML document source code, when the document is parsed, the <
character appears in the output. Why does this happen? Just as the browser converts to
a space, the XML parser will convert any entities that it recognizes into their appropriate char-
acter representations.
Character Entities
Character Entities look a lot like Standard Entities but allow for the display of characters typi-
cally not found in the 7-bit ASCII character set or on the majority of computer keyboards in
the world. A little earlier you took a look at how a space could be inserted into a Web page 2
using the entity. What happens if you want to insert a space into your XML data? You
ASP.NET BASICS
can do so easily by using a Character Entity:
XML FOR
<?xml version=”1.0”?>
<root>
<menuItem> </menuItem>
</root>
An interesting point about this example is that if you leave the preceding code exactly as is but
substitute a physical space for the   entity, Internet Explorer 5 or greater will show the
following when the XML file is opened:
<?xml version=”1.0”?>
<root>
<menuItem/>
</root>
What happened to the space? Right-click the document, choose View Source, and you’ll see
that the space is actually there. However, because it was not specified that the space within the
<menuItem> element needed to be “preserved,” the parser treated the element as if it were
empty. We’ll talk more about handling whitespace a little later in this chapter.
Getting back to Character Entities—if it is necessary to insert the ® character into XML data,
the ® general entity can be used. The following is an example:
<?xml version=”1.0”?>
<root>
<menuItem>®</menuItem>
</root>
If you’re wondering how you can know what Character Entities exist, you can easily see all the
values by running the ASP.NET script shown in Listing 2.10 (increase the upper bound of i as
necessary):
XML for ASP.NET Developers
38
Internal Entities
Thus far you’ve seen that entity references always start with the & character and end with the ;
character. This same structure holds for all types of entities (excluding Parameter Entities, as
you’ll see).
An Internal Entity allows a frequently used piece of information to be defined once within a
DTD and reused as many times as necessary in an XML document. This is exactly how an
include (or a user control) works with ASP.NET. A reusable piece of code (such as a header) is
put into an include file, which is placed on the Web server. This include file can then be used
as often as necessary in other ASP.NET pages. Any changes made to the include file are made
in only one place, which is, of course, very efficient from a maintenance point of view.
The following is an example of how to declare an entity in a DTD:
XML for ASP.NET Basics
39
CHAPTER 2
To use the entity within an XML document, simply wrap the name with the normal entity start
and end characters (& and ;). This is similar to wrapping include files with the <!--# and -->
syntax. Because we haven’t discussed DTDs, you’ll get your first taste of them by seeing an
“internal” DTD that is used to define the companyName entity:
<?xml version=”1.0”?>
<!DOCTYPE root [
<!ENTITY companyName “Tomorrow’s Learning”>
]>
<root>
2
ASP.NET BASICS
<menuItem>
<hyperLink>http://www.TomorrowsLearning.com</hyperLink>
XML FOR
<name>&companyName;</name>
<menuItem>
<hyperLink>/location/default.aspx</hyperLink>
<name>Location of &companyName;</name>
</menuItem>
</menuItem>
</root>
Running this document in Internet Explorer 5 will result in the &companyName; entity being
replaced with the text Tomorrow’s Learning.
External Entities
External Entities function in the same manner as Internal Entities from the XML document’s
perspective. The entity name (wrapped with & and ;) is placed in the XML document and is
replaced with whatever text was specified by the External Entity declaration when the docu-
ment is parsed. The difference between the Internal Entity and External Entity is where the
entity definition occurs.
With an External Entity declaration, the entity declaration is made outside of the DTD in a
separate file. Here’s an example of an External Entity declaration. The actual definition would
occur within the DTD:
<!ENTITY companyName SYSTEM “http://www.TomorrowsLearning.com/companyName.txt”>
This declaration names the entity just like the Internal Entity did but then defines the declara-
tion as a SYSTEM entity. This information is followed by the path to the declaration file contain-
ing the entity’s information.
External Entities can even point to other XML files. This can be very useful in situations
where multiple departments all make small contributions to a “master” XML document. Each
department maintains its own XML document and these “mini” documents are pulled into one
main XML document through the use of External Entities.
XML for ASP.NET Developers
40
Parameter Entities
Updating information that is located in one place makes maintenance of XML documents eas-
ier and more efficient. What if a DTD contains repeated sections, though? Wouldn’t it be nice
to able to declare these sections once and then reuse them throughout the DTD?
Parameter Entities accomplish this exact task through a syntax that is very similar to Internal
and External Entity declarations. The declaration syntax differs from the previously discussed
entities only in the use of a % character followed by a space:
<!ENTITY % children “(location,address,phone+)”>
This Parameter Entity declaration can then be used within the DTD by preceding the entity
name with % and ending it with ;:
%companyName;
Although both character and parameter entities are defined in a DTD, the parameter entity can
be used only within the confines of the DTD itself. It cannot be used in an XML document like
character entities can. Chapter 4 contains a more in-depth discussion of DTDs.
CDATA Sections
There will invariably be times when the data you want to include within an XML document
contains characters that may cause the parser to fail. Earlier you saw that when the < character
was placed into the data, the parser was tricked into thinking that a new element was starting.
As a result, it generated an error.
Aside from using entities to prevent the parser from failing, as shown earlier, the XML lan-
guage also includes the character data (CDATA) section to allow data such as the < or & char-
acters to be included without causing problems when parsed. CDATA sections are unparsed by
the XML parser and treated as text or “character data.” Their content is simply passed through
the parser untouched.
To gain a better understanding of how CDATA sections are handled by the parser, let’s first
take a look at an ASP.NET page with embedded HTML:
<%
for (int i=0;i<10;i++) {
%>
<font color=”#02027a”><b>The current number is:</b></font> <%=i%>
<br>
<%
}
%>
XML for ASP.NET Basics
41
CHAPTER 2
ASP.NET BASICS
<presentation>
XML FOR
<slide number=”1”>
<![CDATA[
<table border=”0”>
<tr>
<td>Hello World</td>
</tr>
</table>
]]>
</slide>
</presentation>
Opening this document in Internet Explorer 5 or later results in the CDATA section being
shown exactly as it was entered into the XML document. CDATA sections even leave spacing
alone. Try tabbing or spacing over the <td>Hello World</td> portion of the CDATA section
to the left or right, and you’ll see that it will display exactly as you type it in the Internet
Explorer 5 or later. Why doesn’t it strip out spacing as we saw in a previous example?
Remember that the parser leaves the text within the CDATA section alone and simply passes it
through untouched.
Although whitespace in the form of spaces can be added to HTML by adding the entity
or to XML by adding the   entity, another method exists that allows whitespace to
XML for ASP.NET Developers
42
remain in the data. Through using this alternative, you can avoid resorting to CDATA sections
or entities.
What is the alternative? The xml:space attribute. This special attribute can be set equal to
“default” or to “preserve”, as shown next:
By setting the xml:space attribute to “preserve”, all whitespace within the element’s data will
be left untouched by the parser. Setting the attribute to “default” allows the parser to strip the
data of any excess whitespace. Use of this attribute is normally reserved for data that contains
whitespace that needs to be left alone, such as with a poem.
NOTE
Internet Explorer 5 or later does not honor the xml:space attribute by default. When
developing client-side code using MSXML (or MSXML3), you can turn this feature on
by setting the preserveWhiteSpace property to true.
• Document developers and user agent designers are constantly discovering new ways to
express their ideas through new markup. In XML, it is relatively easy to introduce new
elements or additional element attributes. The XHTML family is designed to accommo-
date these extensions through XHTML modules and techniques for developing new
XHTML-conforming modules (described in the forthcoming XHTML Modularization
specification). These modules will permit the combination of existing and new feature
sets when you’re developing content and designing new user agents.
• Alternative ways of accessing the Internet are constantly being introduced. Some esti-
mates indicate that by the year 2002, 75% of Internet document viewing will be carried
out on these alternative platforms. The XHTML family is designed with general user
2
ASP.NET BASICS
agent interoperability in mind. Through a new user agent and document-profiling mecha-
XML FOR
nism, servers, proxies, and user agents will be able to perform best-effort content trans-
formation. Ultimately, it will be possible to develop XHTML-conforming content that is
usable by any XHTML-conforming user agent.
Now that you’ve seen the benefits of XHTML, you may be wondering how it differs from
HTML. Although the tags are basically the same, all XHTML tags must be closed, nested, and
cased properly for the XHTML document to be well formed. Attributes must be quoted, must
have an associated value, and cannot be defined more than once on the same element. For
example, the selected attribute shown next is incorrect if XHTML rules are being followed:
<option value=”1” selected>ASP.NET</option>
To make this XHTML compliant, the selected attribute must have a value that is quoted:
<option value=”1” selected=”selected”>ASP.NET</option>
A complete discussion of these rules and many others was given earlier in this chapter.
As its name implies, XHTML is extensible. With HTML you cannot introduce a new tag
unless the device that renders it understands the tag. With XHTML, modules can be created
that tell the device how to work with a custom tag. This allows devices such as Internet-
enabled phones to work with a “down-sized” XHTML module that is geared toward lower
memory consumption, and it allows more powerful applications to work with “extended”
XHTML modules that may include custom tags. In general, XHTML is much more flexible,
efficient, and extensible than HTML because of modularization. For more information on
XHTML modules, visit: http://www.w3.org/TR/2001/REC-xhtml11-20010531/.
So what does an XHTML document look like? Actually, it’s very similar to the HTML docu-
ments you’re used to working with except that it follows the XML rules. A simple XHTML
document is shown next:
XML for ASP.NET Developers
44
This document starts with the XML declaration, which is followed by one of three possible
DTDs. These include a traditional (most of the general public will use this), a strict (allows for
“cleaner” markup without including layout tags), and a frameset DTD (used with frames), as
shown next:
<!DOCTYPE html
PUBLIC “-//W3C//DTD XHTML 1.0 Strict//EN”
“DTD/xhtml1-strict.dtd”>
<!DOCTYPE html
PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”
“DTD/xhtml1-transitional.dtd”>
<!DOCTYPE html
PUBLIC “-//W3C//DTD XHTML 1.0 Frameset//EN”
“DTD/xhtml1-frameset.dtd”>
Aside from the DTD, you’ll notice that a global namespace is also included on the HTML
element along with two language attributes.
Because XHTML documents follow the XML rules, parsers can treat them as regular XML
documents. This provides a lot of power and flexibility when different XML languages such as
XLink and XPointer are used (discussed in Chapter 3, “XPath, XPointer, and XLink”) to
access specific portions of an XHTML document. Because XHTML is available for implemen-
tation today, you should begin creating all HTML documents in a manner that makes them
XHTML compliant.
Because we haven’t discussed the XML parser built in to the .NET platform yet, the sample
application that follows uses what you already know to construct an XML file on-the-fly,
based on user input. By the end of the application, you’ll understand why a parser would make
things a lot easier. Before getting too far into the application code, let’s discuss it in more
detail.
Application Specifics
The application’s purpose is to allow department heads to submit job bid requests for informa-
tion technology contractors. Upon submitting a request, the real-world version of the applica- 2
tion would start a workflow and provide a lot of other functionality that won’t be covered in
ASP.NET BASICS
this sample.
XML FOR
Let’s assume that the business requirements specify that there will be a maximum of 25 bid
requests a week and that after the workflow is started, bid requests need to be stored in an
XML file, which will be used throughout the workflow process. At the end of each month, this
XML file will be imported into a proprietary database using a batch process already in place.
LISTING 2.11 Code Used to Create or Append Information to an XML Document Using
Standard ASP.NET Classes
1: <%@ Import Namespace=”System” %>
2: <%@ Import Namespace=”System.IO” %>
3: <script language=”C#” runat=”server”>
4: string rootTag = “root”;
5: public class CreateXmlFile {
6: HttpRequest Request = null;
7: public CreateXmlFile(HttpRequest ServerRequest) {
8: //Overload Constructor when request object is needed
9: Request = ServerRequest;
10: }
11: public CreateXmlFile() {}
12: public string writeXML(string root,string node,string recordID,
13: string version,string fileName,bool writeEndRoot,bool writeAtts){
14: bool blnExists = false;
15: StreamWriter writer = null;
16: string pathFile = getPath(fileName);
17: //See if the XML file already exists
18: blnExists = File.Exists(pathFile);
19: if (!blnExists) { //Create the file since it doesn’t exist
20: writer = new StreamWriter(File.Open(pathFile,
21: FileMode.Create, FileAccess.Write));
22: writer.WriteLine(“<?xml version=\”” + version + “\”?>”);
23: writer.WriteLine(“<” + root + “>”);
24: } else {
25: writer = new StreamWriter(File.Open(pathFile,
26: FileMode.Append, FileAccess.Write));
27: }
28: writer.WriteLine(“<” + node + “ id=\”” + recordID);
29: if (!writeAtts) {
30: writer.Write(“\”>”);
31: } else {
32: writer.Write(“\” “);
33: }
34: foreach (string x in Request.Form) {
35: if (x.ToUpper()!=”SUBMITBUTTON” && x.IndexOf(“_”) != 0) {
36: if (!writeAtts) {
37: writer.WriteLine(“<” + x + “>” + Request.Form[x] +
38: “</” + x + “>”);
39: } else { //Write out as attributes
40: writer.WriteLine(x + “=\””+Request.Form[x]+”\” “);
41: }
42: }
43: }
XML for ASP.NET Basics
47
CHAPTER 2
ASP.NET BASICS
54: writer.Close();
55: return “”;
XML FOR
56: }
57:
58: public string ReadFile(string file,bool bGetPath) {
59: //Create the File System Object and txtfile
60: string filePath = “”;
61: string fileOut = “”;
62: StreamReader reader = null;
63: filePath = bGetPath?getPath(file):file;
64: try {
65: reader = new StreamReader(File.OpenRead(filePath));
66: fileOut = reader.ReadToEnd(); //Can be expensive
67: }
68: catch (Exception e) {
69: fileOut = “There was an error: “ + e.ToString();
70: }
71: finally {
72: if (reader != null) reader.Close();
73: }
74: return fileOut;
75: }
76:
77: private string getPath(string file) {
78: string path = Request.ServerVariables[“PATH_TRANSLATED”];
79: path = path.Substring(0,path.LastIndexOf(“\\”)+1) + file;
80: return path;
81: }
82: } //End Class
83:
84: public void submitButton_Click(Object Sender, EventArgs E) {
85: string output = “”;
86: string ID = Request.Form[“dateTime”];
87: ID = ID.Replace(‘/’,’-’);
XML for ASP.NET Developers
48
Looking at the preceding code, you can see that a class is specified named CreateXmlFile.
This class accepts the Request object in its constructor. When the page first loads, the main
HTML form is shown to the user. After entering the necessary information, the user clicks a
button named submitButton that triggers the submitButton_Click event handler on the server.
This handler instantiates the CreateXmlFile class and calls its writeXml() method. This
method accepts the parameters listed in Table 2.2
ASP.NET BASICS
already exist, one is created (lines 20–23) and the XML declaration and root tag items are
XML FOR
written to the file. The function then proceeds to loop through each form element, writing each
element’s name and value to the document (lines 34–43). If the writeXML() method’s
writeAtts parameter is true, this process will add the form element name/value pairs to the
document as attributes. Otherwise, it will add them as child elements of the node parameter. If
the writeEnd
Root parameter is true, the closing root element tag is written to the text file. This would be
used only if one request were being captured per XML document. If the writeEndRoot para-
meter is false, no closing root tag is written to the document.
Wait a minute! Didn’t we just spend an entire chapter discussing how important it is to follow
the XML rules and close all tags so that XML documents are well formed? Well. . .yes we did.
But because we’re not working with an XML parser, this is a necessary evil for the application
to handle more than one bid request update to the XML file. Because the File object doesn’t
have a method allowing us to delete one specific line easily, we can’t insert the final root tag
until all bid requests for the month have been entered. After we discuss manipulating XML
documents using the DOM in Chapter 6, you’ll see how this problem can be rectified.
A potential resource-intensive alternative would be to use the File object’s ReadToEnd()
method to read the entire contents of an existing XML document into a variable, replace the
existing end root tag with empty strings, add a new bid request, and then write out the end root
tag to the end of the document. This process would occur with each bid request addition and
could become a problem if the client were to start entering more than 25 requests per week as
specified. A few other workarounds to the problem exist as well, although we won’t focus on
them here.
Continuing with the current (non–parser-based) solution, after a bid request has been added to
the XML document, you are presented with an opportunity to see the XML document. The
viewXml_Click event handler (lines 102–108) contains the code that sets the Response.
ContentType to “text/xml”, loads the non–well-formed XML document, dynamically adds
the closing root tag, and then passes the now well-formed document to the browser (Internet
Explorer 5or later required).
XML for ASP.NET Developers
50
Application Summary
It’s obvious that this solution isn’t an optimal one. If the client ever anticipates having several
thousand bid requests a month, you can imagine how big the file could get and what type of
concurrency problems would be created. However, as long as the end root tag is added to the
XML document (making it well formed) before the batch process runs to update the client’s
database, it is one potential solution that will work. As mentioned previously, a much more
efficient method of adding to or updating an XML document will be presented in Chapter 6.
Summary
We’ve covered a lot of rules and concepts in this chapter. Although everything discussed plays
an important role in creating XML documents, it’s extremely important that you take away
from this chapter a good knowledge of XML’s rules. Documents that do not follow the rules
may look good at first, but as you saw in the “Well-Formed XML Documents” section, the
parser has no patience for rule bending and will let you know with an error message.
In the next chapter, we’ll begin looking at a few of XML’s relatives that allow information
located both in local and remote XML documents to be queried and/or manipulated. These
include XPath, XPointer, and XLink.
XPath, XPointer, and XLink CHAPTER
3
IN THIS CHAPTER
• Meet a Few of XML’s Relatives 52
• XLink—Resource Relationship
Management 76
Although syntactically different, XPath provides the same access to XML document data as
T-SQL does to SQL Server data. The XPath language is used by XSLT (covered in Chapter 7,
“Transforming XML with XSLT and ASP.NET”) and XPointer to access data found within
specific XML document elements, attributes, and so on. 3
XPOINTER, AND
So what does XPath syntax look like? You may be surprised to learn that its syntax does not
resemble XML’s syntax at all. In some ways it resembles a syntax that you are more than
XPATH,
XLINK
likely used to working with: the Windows file system.
The Windows file system emulates a hierarchical structure with the drive letter representing the
root of all other files and folders on that drive. An XML document is structured hierarchically
with the root element (also called the root node) containing different child elements (or
nodes). These child elements may themselves contain other child elements.
To access a folder on one of your drives ( C: for example), your computer follows a path simi-
lar to the following:
c:\temp\subFolder1\subFolder2\xmlFile.xml
TIP
As a point of review, you cannot simply add C: as the root element name. Remember
in Chapter 2 that we discussed the concept of qualified namespaces. Putting the
colon in the root name makes the parser think that C was declared as a qualified
namespace somewhere in the document. Because it can’t find the declaration (or the
element name after the colon), the parser will raise an error.
If you wanted to access subFolder2 in the preceding XML document using XPath syntax, you
would simply type the following:
C/temp/subFolder1/subFolder2
You could also type a shorthand version. This version of the query searches for all subFolder2
nodes located anywhere within the XML document:
//subFolder2
You’re now an XPath expert! Well, the word “expert” may be a little strong at this point, but
your experience in working with the Windows file system should help you in understanding
how XPath works. XPath is, of course, much more powerful than this simple example. Aside
from its basic syntax, the XPath language also includes its own set of functions to provide
additional utilities such as formatting strings and numbers or performing mathematical
calculations.
Before going into more detail about XPath, it’s important that you realize that it is not
designed as a standalone language. Going back to the Windows file system comparison, with-
out having access to a DOS command prompt, File Manager, or Windows Explorer, typing the
path to a file wouldn’t get you very far and you definitely wouldn’t find the file or folder you
were looking for. The syntax you saw earlier to access a file is functional only when used in
conjunction with another language or program (such as a command prompt). XPath was
designed in the same manner. It is useful only when used with another language such as
XPointer or XSLT.
XPath Basics
The XPath specification defines a term named ”Location Steps” that sums up how the XPath
language actually works. To get to a particular node’s location, you have to list the steps to get
there using an XPath expression or pattern. Going back to the first XPath sample, we defined
the starting point as C and listed each step that should be followed to walk through the element
nodes to get to the destination data. Each step of the process was separated by a / character.
XPath, XPointer, and XLink
55
CHAPTER 3
Let’s clarify this a little more through giving a simple real-life example. Getting to a node
within a document is very similar to explaining your family lineage. If a friend asked you
about a portion of your family line going back to your great grandfather, you may give the
following information using XPath syntax:
Great_Grandfather/Grandmother/Mother/Self
You can see from this sample that each step in your family lineage is separated by the /
character.
Another important term used in the XPath specification is that of the context node. The context
node is the node being evaluated at each step of an XPath expression. In your family lineage
example, the context node is first your Great_Grandfather. The context changes to the
Grandmother node and then moves to the Mother node. Finally, the context node is Self. This
same logic applies to nodes being evaluated at each step of an XPath expression.
The context node becomes important as evaluations are being done on it. If someone asks you
how many children your ______ had, it would be difficult to answer the question unless the
blank was filled in (unless you can read minds of course). You need to know where to start (the
context) in order to reply. If, however, someone asks you how many children your great grand- 3
father had, you can now provide an answer because you have a context from which to start.
XPOINTER, AND
The path to a particular node within an XML document is constructed from three pieces of
XPATH,
XLINK
information:
• An Axis Type
• A Node Test
• Predicates (filtering)
The following sections explain each of these items in turn. After that discussion, we’ll discuss
XPath functions and get into some examples so that you can see more XPath expressions at
work.
Axis Types
The family lineage XPath statement we looked at earlier took advantage of some XPath abbre-
viations to make things look a little nicer. Without using abbreviations, the same statement
would look like this:
Great_Grandfather/child::Grandmother/child::Mother/child::Self
Don’t worry about the :: characters. We’re not going to define any C++ interfaces or anything
like that. The child:: syntax is one type of axis defined by the W3C that can be used when
you’re trying to locate a node within a document. Each axis is followed by two colons (::)
when used within an XPath expression. Axes are somewhat intimidating at first glance but are
XML for ASP.NET Developers
56
very useful when you know a few basics about how they work. Table 3.1 contains all the avail-
able axes as defined in the XPath specification.
Although many of the axes, such as self, child, and parent are self-explanatory, a few are
not as obvious at first. Let’s take a look at a few axes examples based on Listing 3.1.
Assuming that the context node is <father>, the descendant axis to this node would, as the
axis description says, select all descendants, which in this case would be the <self>,
<sister>, <brother>, and <child> nodes. Which nodes would be included in the descendant-
or-self axis if the <father> node is still the context node? This axis would contain the
<father>, <self>, <sister>, <brother>, and <child> nodes. Keep in mind that although this
example uses the element named self, this has nothing to do with the word self as contained
in the descendant-or-self axis. self as used in the axis name simply refers to the context
node, which is <father> in this example. 3
XPOINTER, AND
The following are a few more examples that simply list the axis name and nodes contained
within it. Assume that the <self> node is now the context node:
XPATH,
XLINK
Axis Included Nodes
following-sibling <sister>, <brother>
preceding-sibling none
ancestor <father>, <grandMother>, <greatGrandfather>,
<familyLine>
preceding none
Node Tests
Node tests let you specify what type of node to locate relative to the context node. Each of the
previously discussed axes have their own principal node type. For those axes that can contain
XML for ASP.NET Developers
58
elements, the principle node type is element. Because the attribute axes can contain only attrib-
utes, its principle node type is. . . you guessed it, attribute.
The previous examples have performed node tests to check for element nodes only. However,
you could have done other types of node tests if you had wanted to get to a different location
in the document other than an element. The other node tests you could have used include
• The * wildcard character, which includes all items in the current axis. The wildcard
works in a manner similar to the SQL * wildcard character used in SELECT statements.
• text(), which includes all text nodes in the current axis.
• node(), which includes all nodes in the current axis.
• comment(), which includes all comments in the current axis.
• processing-instruction(), which includes all processing-instructions in the current axis.
Although you’ll have the opportunity to see plenty of examples of node tests in action, the fol-
lowing are a few simple examples with explanations to get you started:
Predicates
Using axes and node tests can help in reaching a specific location within an XML document.
But what if the context node contains four children that are elements and you want to select
only the third child element? You can use the child axis and element node test to get to the four
children, but neither the axes nor node tests help you to select the third element only.
Predicates, otherwise known as WHERE clauses, help solve this dilemma for you. A predicate
helps to filter out unwanted nodes by taking advantage of built-in functions. This process
is very similar to using the RowFilter property of the ADO.NET DataView class. Using
ADO.NET, filtering out records that don’t have a specific ID (as defined in intID below) is
as easy as typing the following code:
dataView.RowFilter = “ID = “ + intID;
Using XPath, filtering out nodes that don’t meet specific requirements is as easy as typing
child::sibling[position()=3]
This XPath statement uses a predicate to select the third sibling child of the context node.
XPath, XPointer, and XLink
59
CHAPTER 3
You can see that a predicate is started and ended by using the [ and ] character tokens.
XPath has several built-in functions, which you’ll learn more about in a moment, that can be
used within the predicate statement. The preceding example used the position() function.
Using one or more of these functions in combination with comparative operators, the processor
performs the necessary checks on the current node-set to see which nodes should remain and
should be excluded. If you’re wondering what a node-set is, think of it as another form of an
ADO.NET DataSet. The difference is that the node-set contains a collection of XML nodes,
whereas the DataSet contains records returned from a data source.
The predicate statement as a whole produces a Boolean value after it is evaluated. Either the
predicate statement is true and the node being evaluated is included in the node-set, or it is
false and the node is excluded from the node-set. This Boolean value is derived by comparing
the result of a function call to a value using comparative operators, including
• =, !=
• -, +, *
• and, or
3
• div, mod
XPOINTER, AND
• | (union operator)
XPATH,
XLINK
Your first look at predicates showed how to get at the third sibling child node of the context
node. If your goal were to get the third and fourth child sibling nodes, you could use one of the
following statements:
child::sibling[position() >= 3]
child::sibling[position() > 2]
child::*[position() = 3 or position() = 4]
All location steps in an XPath expression will follow this syntax. We can break the syntax into
pieces: the XPath statement must first specify the type of axis to traverse. The child axis is the
default if no axis is specified. After it is on the proper axis, the node test is performed to check
whether a particular node exists. In cases where a specific node must be accessed, a predicate
can be added to the XPath expression, which acts as a filter.
XML for ASP.NET Developers
60
Samples of using this syntax are shown later in the chapter, along with abbreviated forms that
can be used to shorten the location step framework. Before these are shown, however, it’s
important that you understand what native functions exist in the XPath recommendation.
Understanding these functions will allow you to fully leverage predicates.
XPath Functions
Many functions are available other than the position() function shown earlier. The XPath rec-
ommendation categorizes each of the predicate functions based on its purpose and functional-
ity. These categories include node-set functions, string functions, number functions, and
Boolean functions. We’ll first discuss the node-set functions.
XPOINTER, AND
Example: child::*[namespace-uri() = “http://
XPATH,
XLINK
www.someURL.com”]
You’ll notice that there is no specific node-set function to extract a namespace prefix (the me in
me:local-name for instance) should one exist on a node. The local-name() function allows
you to get the node’s name, the name() function gets the prefix and local-name together, and
the namespace-uri() function returns only the namespace URI. This certainly appears to pre-
sent a problem because knowing the prefix associated with a particular node may be a big part
of your applications, depending on the number of contributors to an XML document. Fortu-
nately, this problem can be solved by taking advantage of node-set functions used in conjunc-
tion with XPath string functions.
XPOINTER, AND
a value of 0 being returned. The example returns a value
XPATH,
XLINK
of 7.
Example: string-length(“Sibling”)
normalize-space(string) This function is similar to the Trim() function in VB,
although it also replaces internal sections of whitespace
with single spaces. The example returns “How are you?”
Example: normalize-space(“ How are you? “)
translate(string, This function performs character substitutions. Characters
from,to) that exist in the first two arguments are converted to the
corresponding characters found in the third argument. This
function is best understood by seeing an example. The
example shown below will return “HOW ARE YOU?”
Example: translate(“how are you?”,”aehouwy”,
”AEHOUWY”)
The next example will return “I AM fiNE”
Example: translate(“i am fine”,”amne”,”AMNE”)
XML for ASP.NET Developers
64
XPOINTER, AND
boolean(value) This function returns a Boolean value (true or false) depending
XPATH,
XLINK
on the value passed into the function. The conversion of a value
to Boolean normally occurs automatically as needed. However,
the boolean() function is useful to explicitly convert a value to a
Boolean when necessary. For example, if you have a node named
customerExists that contains the text “true,” you can convert this
value from a string to a Boolean by using the following syntax:
Example: boolean(/root/customerName/customerExists)
To convert a 0 (false) or 1 (true) to a Boolean, use the fol-
lowing syntax:
Example: boolean(1)
not(boolean) This function returns the opposite of what is passed into it. Thus,
if a true is passed in, a false will be returned and vice versa. The
example returns a true.
Example: not(false)
true() This function returns true. This can be used when a constant
Boolean value is needed in an XPath expression.
XML for ASP.NET Developers
66
The following are a few examples shown first with the non-abbreviated syntax and then fol-
lowed by the abbreviated syntax. Some of the examples include more than two XPath expres-
sions to help you see other ways of accomplishing the same task. The examples are based on
the following XML document:
<?xml version=”1.0”?>
<golfers>
<golfer skill=”excellent” handicap=”4” clubs=”Taylor Made” id=”1111”>
<name>
<firstName>Heedy</firstName>
<lastName>Wahlin</lastName>
</name>
<favoriteCourses>
<course city=”Pinetop” state=”AZ” name=”Pinetop Lakes CC”/>
<course city=”Phoenix” state=”AZ” name=”Ocotillo”/>
<course city=”Snowflake” state=”AZ” name=”Silver Creek”/>
</favoriteCourses>
</golfer>
<golfer skill=”moderate” handicap=”8” clubs=”Taylor Made” id=”2222”>
<name>
<firstName>Dan</firstName> 3
<lastName>Wahlin</lastName>
XPOINTER, AND
</name>
<favoriteCourses>
XPATH,
XLINK
<course city=”Pinetop” state=”AZ” name=”Pinetop Lakes CC”/>
<course city=”Pinetop” state=”AZ” name=”White Mountain CC”/>
<course city=”Springville” state=”UT” name=”Hobble Creek”/>
</favoriteCourses>
</golfer>
</golfers>
XPath Examples
Select the id attribute of the first golfer element:
/golfers/descendant::golfer[position()=1]/attribute::id
/golfers/golfer[1]/@id
Select all descendants of the golfer element. This includes the name and favoriteCourses
elements:
/golfers/descendant::golfer/*
//golfer/*
/golfers/golfer/*
XML for ASP.NET Developers
68
Select the name attribute of the third course element associated with the second golfer element:
/golfers/child::golfer[position()=2]/child::favoriteCourses/
➥child::course[position()=3]/attribute::name
/golfers/golfer[2]/favoriteCourses/course[3]/@name
//golfer[2]/favoriteCourses/course[3]/@name
Select the first golfer element and all children. This selects the name and favoriteCourses
elements:
/golfers/child::golfer[position()=1]/child::*
/golfers/golfer[1]/*
Select the second golfer element’s course element having a name attribute equal to the first
golfer element’s first course element name attribute (a little confusing unless you break it
down into steps):
/golfers/child::golfer[position()=2]/child::favoriteCourses/child::course/
➥attribute::name[. =
/golfers/child::golfer[position()=1]/child::favoriteCourses/
➥child::course[position()=1]/attribute::name]
/golfers/golfer[2]/favoriteCourses/course/@name[. = /golfers/golfer[1]/
➥favoriteCourses/course[1]/@name]
Select the last course element’s name attribute that is located under the second golfer
element:
/golfers/child::golfer[position()=2]/favoriteCourses/course[last()]/@name
//golfer[2]//course[last()]/@name
/golfers/golfer[2]/favoriteCourses/course[last()]/@name
Select the city attribute of all course elements that have a name attribute starting with the
letter “P”:
/golfers/child::golfer/child::favoriteCourses/child::course[starts-with
➥(@name,’P’)]/attribute::city
/golfers/golfer/favoriteCourses/course[starts-with(@name,’P’)]/@city
Select the first golfer element’s skill attribute and capitalize it:
translate(/golfers/child::golfer[position()=1]/attribute::skill,
➥‘abcdefghijklmnopqrstuvwxyz’,’ABCDEFGHIJKLMNOPQRSTUVWXYZ’)
translate(/golfers/golfer[1]/@skill,’abcdefghijklmnopqrstuvwxyz’,
➥‘ABCDEFGHIJKLMNOPQRSTUVWXYZ’)
XPath, XPointer, and XLink
69
CHAPTER 3
After seeing the previous XPath statements, you may be wondering how you can test them to
see if they actually work. You may also want to practice creating some of your own XPath
statements. Fortunately, an excellent browser-based tool for testing XPath statements was
created by Aaron Skonnard of DevelopMentor. The tool can be downloaded from http://
staff.develop.com/aarons/xmllinks.htm. Figure 3.1 shows an example of the tool in
action.
XPOINTER, AND
XPATH,
XLINK
FIGURE 3.1
Using the XPath Interactive Expression Builder.
XPointer supports addressing into the internal structures of XML documents. It allows for
examination of a document’s hierarchical structure and choice of its internal parts based on
various properties, such as element types, attribute values, character content, and relative posi-
tion. In particular, it provides for specific reference to elements, character strings, and other
parts of XML documents, whether or not they bear an explicit ID attribute.
Many of the concepts mentioned in this statement strongly resemble functionality already
available in the XPath language, which may lead you to believe that much is duplicated
between the two. However, this is not the case. XPointer simply extends XPath’s functional
utility, as you’ll see in the following sections. Your current XPath knowledge will be applicable
to many XPointer concepts.
XPointer Basics
At this point you may be wondering what the difference is between XPath and XPointer.
Because both seem to allow access to specific items found within an XML document, can
XPointer be used for something different? Obviously the answer to this question is “yes,” given
all the work that has gone into creating the language. XPointer differs from XPath because it
provides a mechanism for accessing specific content, referred to as document fragments,
located in local or remote documents. This content isn’t limited just to nodes. XPointer even
allows individual characters in a text string to be selected.
Imagine having an XML document containing research data that is composed of three sections,
including an introduction, body, and conclusion. The body and conclusion portions of the doc-
ument do not exist within this XML document. They are located externally within two XML
documents that contain information other than simply body or summary text.
Using XPath alone, there is no mechanism for gaining access to the body and summary text in
these remote documents. XPath is designed to locate specific nodes within a local XML docu-
ment. This is where XPointer comes into play. Using portions of XPath syntax, XPointer
allows the body and summary text sections to be gathered from the remote documents and
placed into the current XML document. All this can be done without having to load the entire
remote document (or documents in this case). Comparing this functionality to HTML, combin-
ing these languages allows you to link to a specific area of a document and “grab” its content
without having to load the entire page and then scroll to the content as is done with HTML
anchors.
The capability to access data down to the individual character level opens up a whole new
world for data sharing and exchange. Before getting too much further into the details, let’s
cover a few key XPointer definitions:
XPath, XPointer, and XLink
71
CHAPTER 3
Term Definition
sub-resource The portion of the XML document that XPointer identifies. Although
the entire XML document would be considered the resource, the
portion being accessed by XPointer would be the subresource.
point The W3C defines a point simply as “A position within XML infor-
mation.” A point may represent a node within an XML document or
simply a specific location within a node’s XML character data. The
combination of a container node and index value define a point’s
location. A point may be a position before any character or may pre-
cede or follow a node. A point can never exist on a character,
though, only before or after it.
As an example, the “T” character in the following document frag-
ment has a container of type element and has an index of 0. The “e”
would have an index of 1 and the “d” character an index of 2:
<golfer>
<firstName>Ted</firstName>
</golfer>
character point A point with a container node that has no child nodes. Examples 3
include a point within text, comments, processing instructions, and
XPOINTER, AND
so on.
XPATH,
XLINK
node point A point with a container node that has child nodes.
range A selection of XML content located between two end points. When
you highlight a section of text in a program (such as Word) using
the cursor, the selected area could be considered a range because it
has starting and ending points.
collapsed range A range that has equal start and end points. For example, if we start
a point at character three and end a point at character three, the
ranged is considered to be collapsed.
location Similar to a node in XPath. However, because XPointers can begin
in one paragraph and end in another and do not have to specify
nodes in particular, a location can be a node, point, or range.
location-set This is similar to a node-set created by an XPath expression.
However, aside from including nodes, a location-set can also
include points and/or ranges.
singleton This term is reserved for situations where one range, string range, or
single node is located by an XPointer. Multiple areas of XML con-
tent identified by an XPointer are not considered to be singletons.
fragment identifier An XPointer identifier (expression) that has an escaped URI.
Escaped refers to replacing characters such as < with their URI
escape value, which would be < in this case.
XML for ASP.NET Developers
72
Points and ranges are used when working with XPointer expressions. Points determine where
to start and end a search for XML content within a document, and a range contains the XML
content located between two points. The syntax to create XPointer expressions using two
points is as follows:
http://www.someURL.com/someDOC.xml#xpointer(<point1> to <point2>)
This syntax would link to the range specified between point1 and point2 and load the entire
document. You’ll notice that this is similar to how the HTML anchor currently works, although
XPointer allows for much more flexibility, as you’ll see in the next few sections. After parsers
support this feature, you’ll be able to pick out a section of XML within a remote document
with pinpoint accuracy.
XPointer Functions
The additional functions available in the XPointer language along with their corresponding
descriptions are listed in Table 3.6. One interesting point about these functions is that they can
be called after a / character in a location step (that is, node/function()). This is different
from the XPath language where calling a function after the / character would normally result
in the parser raising an error (exceptions to this exist, such as the count() and sum() func-
tions to name just two).
XPOINTER, AND
will start just after the “L” character (four characters in)
XPATH,
and contain the five characters that come after the fourth
XLINK
(represented by [4]) “XML” string:
Example: string-range(//title,”XML”,4,5)[4]
range(location-set) This function returns ranges that match up with the loca-
tions specified in the location-set argument. The example
would return ranges for all folder elements that have an
attribute named path starting with the letter “D.” The
range starts just before the folder node’s beginning tag
and ends just after its ending tag:
Example: range(//folder[starts-with(@path,”D”)])
range-inside(location-set) This function returns ranges that match up with the con-
tent specified in the location-set argument. This is similar
to the innerText property found in DHTML, which also
returns the text between a start and end tag. It differs
from the range() function in that it includes only a
node’s content, not the node and its content. The example
would return ranges for all folder elements that have an
attribute named path starting with the letter “D.” The
range starts just after the folder node’s beginning tag and
ends just before its ending tag:
XML for ASP.NET Developers
74
XPointer Errors
Errors in XPointer can be handled differently than errors in XPath. In situations where an
XPath expression finds no matching locations within the XML document, an empty node-set is
returned and no error is raised. However, XPointer must be stricter with error handling because
it allows access to remote documents. XPointer is required to raise an error if a fragment iden-
tifier (expression) returns no results. The following is a summary of the three errors that can be
associated with XPointer and their associated description:
Error Description
Syntax Error This error occurs when an XPointer expression (also
called a fragment identifier) contains incorrect syntax.
Resource Error This error occurs when a resource document does not
contain well-formed XML. The fragment identifier
(XPointer expression) may be correct but be unable to
create a point or range because of resource document
problems.
Subresource Error This error occurs when an XPointer expression is syntac-
tically correct, the resource contains well-formed XML, 3
but no locations are returned. As mentioned, this type of
XPOINTER, AND
error does not occur in XPath, because empty node-sets
XPATH,
are acceptable there.
XLINK
XPointer Examples
As the XPointer language is moved to the W3C recommended status, more and more examples
of using it with supporting parsers will become available. Until then, here are a few examples of
using XPointer. The examples are prefixed by a remote resource name of www.someURL.com:
The #/1/3/4 syntax tells XPointer to go to remoteDocument.xml and access the first element
found there. After finding this element, find its third child element. Now choose this element’s
fourth child element.
The following example will pull the content between the first and third sibling nodes:
http://www.someURL.com/remoteDocument.xml#xpointer(//father/sibling[1] to
➥//father/sibling[3])
The next example will return all ranges within the /golfers/golfer/name context that contain
a substring equal to “Joe”:
http://www.someURL.com/remoteDocument.xml#xpointer(string-range(/golfers/
➥golfer/name,”Joe”))
Finally, the following document will first try to find a golfer element’s fourth name node. If
that location cannot be found, it will try to find the last name node:
http://www.someURL.com/remoteDocument.xml#xpointer(//golfer/name[4])
➥xpointer(//golfer/name[last()])
You may recall a short discussion we had concerning HTML linking limitations at the begin-
ning of this chapter. Although HTML links work quite well in a browser, some of the limita-
tions found in the HTML link include the following:
• HTML allows links only between two resources.
• HTML links have to be embedded in the originating link’s file. This can cause mainte-
nance headaches.
• HTML anchors can help locate specific information within a document but require that
the entire page be loaded. This wastes bandwidth.
• HTML links are unidirectional. They can point only in one direction.
• The HTML link has few attributes for customizing linking capabilities based on different
linking needs.
• Updating a target document with anchors is not possible without having “write” access
to that document.
• HTML links require specific element tags to work (<a> for example). This makes it diffi-
cult to add linking capabilities to other elements.
A few of these limitations are addressed by the XPointer language because it allows specific 3
content fragments to be accessed in remote XML documents. Although this is a great step for-
XPOINTER, AND
ward, the old HTML link can be improved on in many other ways. XLink strives to make these
XPATH,
XLINK
improvements a reality by adding new linking capabilities to documents that comply with the
XML version 1.0 recommendation.
XLink Basics
The XLink specification doesn’t actually mention linking different documents. Rather, it
defines a structure that consists of links between “resources.” These resources could be XML
documents, media files (images, movies, or music), database information, or any other type of
resource that needs to be referenced through a link.
Adding more functionality to any type of established item, such as the HTML link, is often-
times associated with an overall increase in complexity. This is not necessarily the case with
XLink. After you understand its keywords, elements, attributes, and functionality, you’ll see
that it looks just like regular XML.
Although the XLink language allows you to define links the same way as you do in HTML, it
does bring with it more advanced attribute and element definitions that help in linking two,
three, or more resources in a uniform manner. Understanding the various attributes and defini-
tions that come with the XLink language is important to leverage its linking power.
XML for ASP.NET Developers
78
Before learning about the attributes and elements that exist in XPath, let’s take a look at some
examples of what XPath syntax looks like. First let’s write out the syntax for what is coined a
“simple link.” A simple link functions the same as a link found within an HTML page:
<golfer xmlns:xlink=”http://www.w3.org/1999/xlink/”>
<name xlink:type=”simple” xlink:href=”http://www.someURL.com/row.aspx”>
John Doe
</name>
</golfer>
The following is an example of an extended link containing four different resources. This type
of linking is not possible using HTML’s anchor tag:
<xlink:extended
xmlns:xlink=”http://www.w3.org/1999/xlink/”
role=”foursome”
title=”Golfing foursome”
>
<xlink:locator href=”dad.xml”
role=”golfer1”
title=”First golfer in foursome”
/>
<xlink:locator href=”aaronhorne.xml”
role=”golfer2”
title=”Second golfer in foursome”
/>
<xlink:locator href=”toddwahlin.xml”
role=”golfer3”
title=”Third golfer in foursome”
/>
<xlink:locator href=”michellehorne.xml”
role=”golfer4”
title=”Fourth golfer in foursome”
/>
<xlink:arc from=”golfer1”
to=”golfer2”
actuate=”onRequest”
show=”embed”
/>
<xlink:arc from=”golfer3”
to=”golfer4”
actuate=”onRequest”
show=”embed”
/>
</xlink:extended>
XPath, XPointer, and XLink
79
CHAPTER 3
I’ll defer an in-depth discussion of the different elements and attributes being used here until
later in the chapter. You can see, however, that after you understand a few basic concepts con-
cerning new elements and attributes defined in the XLink specification, implementing the
XLink language isn’t much different than working with normal XML documents.
XPOINTER, AND
Example: <xlink:arc from=”resource1” to=”resource2”/>
XPATH,
XLINK
Extended link An extended link associates many local and/or remote resources
together. The link is considered inline if any of the resources are
local and considered out-of-line if none are local.
Inline link Defined by the W3C as “A link where some content in the link-
ing element serves, by virtue of its presence inside the linking
element, as a participating resource.” Use of the <a> tag in
HTML is considered an inline link.
Locator Data used to identify a resource in a link. Identification occurs
by specifying a URI or other type of identity.
Linkbase A file external of any resource that contains an extended link.
This type of file is similar to an include file in ASP.NET because
it helps maintain specific information referenced by many
resources in one location.
Multidirectional link A multidirectional link can be started by any of the resources
participating in the link. As such, the HTML link would be
excluded from this definition because it can initiate only a one-
way link (hitting the Back button doesn’t count).
XML for ASP.NET Developers
80
Now that you’re familiar with a few of XLink’s definitions and keywords, let’s take a look at
some attributes that are used in building simple and extended links.
XLink Attributes
The XLink language specifies several attributes that add various linking capabilities. A listing
of these attributes with their corresponding description can be found in Table 3.8.
XPOINTER, AND
xlink:show This determines how content identified by a link will be shown.
XPATH,
XLINK
The possible values are replace, embed, new, other, and none.
The replace value functions in the same manner as linking
between two pages in HTML, whereas the embed value would be
similar to the function the <img> tag element performs.
xlink:actuate The actuate attribute specifies how the link should be traversed
from a timing perspective. Rather than always being triggered by
human interaction, the link can be set to be triggered by an appli-
cation. Possible values include onRequest, onLoad, other, and
none.
xlink:to This attribute is used only with an arc element in an extended
link to specify the end point of a link. Its value is equal to the
label attribute on a resource or locator type element.
xlink:from This attribute is used only with an arc element in an extended
link to specify the start point of a link. Its value is equal to the
label attribute on a resource or locator type element.
XML for ASP.NET Developers
82
The xlink:type attribute is specified as “simple” and the xlink:href lists the link URI. This
method of declaring a link is more efficient than the current HTML linking implementation. To
understand why, imagine trying to add linking capability to a <p> tag in HTML without wrap-
ping it with the <a> and </a> tags (or using scripting). Using attributes allows a link to be
added to any element quite easily.
Although you learned a little about XLink attributes earlier, let’s take a look at two interesting
attributes that should prove to be very useful in applications that implement XLink in the
future. The actuate attribute can take on values of onLoad, onRequest, other, and none. The
onRequest value means that the link is traversed when the user requests it. The onLoad value is
used to traverse a link immediately after a document completes loading. When the show
attribute is equal to embed and actuate is equal to onLoad, a useful way of embedding content
from outside resources can be accomplished. When combined with XPointer, these technolo-
gies could be used to create the research XML document discussed earlier that linked to intro-
duction, body, and summary sections located in remote documents.
XPath, XPointer, and XLink
83
CHAPTER 3
Resource 3 Resource 4
XPOINTER, AND
XPATH,
XLINK
Resource 1 Resource 2
FIGURE 3.2
Extended link relationships.
At first glance, this figure may look very similar to simple links between resources, as in
HTML. However, the extended link includes all these resources in a relationship and then spec-
ifies how the links should be traversed. Listing 3.2 shows how these links could be written
using extended link XPath syntax. It’s important to know that the extendLink and loc element
names were chosen to make the sample easier to understand. These element names are not part
of the XLink specification. In fact, any element name could be used.
XML for ASP.NET Developers
84
The extended element type shown in Listing 3.2 acts as a container for four different resource
links. It contains some of the same attributes you saw in the simple element type shown ear-
lier, and it also declares the XLink qualified namespace. Inside this container are four locator
elements that specify the URI to each resource. Going back to Figure 3.2, the small boxes
located on each resource would represent that resource’s locator.
Although the code shown in Listing 3.2 specifies the locators for each resource and groups
each resource in an extended container, it does not say how links between the resources should
be traversed. Should Resource 1 link to Resource 3? Should Resource 4 link to Resource 3?
The listing doesn’t mention anything about the way the resources should be linked.
To accommodate the need for specifying how links should be traversed, the XLink language
introduces the arc concept. An arc can be thought of as a traversal path that specifies how to
link from one resource and to another. Listing 3.3 adds on to Listing 3.2’s code by adding
information about link traversals through the use of arc element types.
XPOINTER, AND
By specifying the arc path, you now know the locations of four resources as well as how each
link between the resources should be traversed. The arc elements listed previously tell the
XPATH,
XLINK
extended link that resource1 links to resource2, which links to resource3, which links to
resource4. If you had wanted resource4 to link back to resource1, you could have applied
another XLink type attribute to an element and specified the additional arc path using the from
and to attributes.
Any variety of arc paths can be specified as long as all paths are unique. All this is done with-
out having to actually add a physical link inside of resource3 that goes to resource4, as is
the case when you link to a resource that you have no control over. Exactly how will
resource3 link to resource4 if the link isn’t physically embedded in resource3? To answer
this question completely, we will have to wait for the first XLink-capable applications to
appear on the scene and analyze how this feature is implemented.
One interesting point made in the XLink specification is that if a from or to attribute is miss-
ing in an arc type element, an arc will be created in the appropriate direction to all locator type
elements with label attributes located within the extended link grouping. To clarify this less
intuitive concept, the W3C provides the following example:
<extendedlink xlink:type=”extended” xmlns:xlink=”http://www.w3.org/1999/xlink”>
<loc xlink:type=”locator” xlink:href=”...” xlink:label=”parent”
➥xlink:title=”p1”/>
XML for ASP.NET Developers
86
Based on each locator type’s title attribute, the following arcs would be assumed because there
are missing from and to attributes in the arc type element:
p1-c1, p1-c2, p1-c3, p2-c1, p2-c2, p2-c3, c1-c1, c1-c2, c1-c3, c2-c1, c2-c2, c2-c3,
c3-c1, c3-c2, and c3-c3
As you can see, this rule causes a type of cross-join to occur that involves each locator type
element. Although all possible arcs are pursued, the uniqueness of each arc is maintained.
The extended link automatically allows links between resources to be organized and grouped.
For this extended link to be useful, it would need to be embedded in one of the resources. This
is called an inline extended link because the resource that contains the embedded link informa-
tion would be editable, in contrast to a third-party read-only resource. After the extended link
is inserted, the relationships between the different resources specified in the locator type ele-
ments can be managed.
Resource 3 Resource 4
External
Linkset
Resource 1 Resource 2
FIGURE 3.3
An out-of-line extended link.
For an external linkset document to be used, a resource needs to know where the external 3
linkset document is located. This is accomplished by placing code similar to the following into
XPOINTER, AND
an inline resource:
XPATH,
XLINK
<basesloaded xlink:type=”extended” xmlns:xlink=”http://www.w3.org/1999/xlink”>
<startrsrc xlink:type=”locator” xlink:label=”spec” xlink:href=”spec.xml”/>
<linkbase xlink:type=”locator” xlink:label=”linkbase” xlink:href=
➥”linkbase.xml”/>
<load xlink:type=”arc” xlink:from=”spec” xlink:to=”linkbase”
xlink:actuate=”onLoad”
xlink:arcrole=”http://www.w3.org/1999/xlink/properties/linkbase”
/>
</basesloaded>
Maintenance of links suddenly becomes much easier through the use of linkbases and external
linksets.
Summary
In this chapter we covered a lot of technologies related to XML. Although the XPointer and
XLink languages have yet to be implemented fully, they are promising technologies that will
make developing with XML even more powerful. In Chapters 6 and 7, you will see how useful
the XPath language can be to access data contained within an XML document using the DOM
or XSLT. However, before jumping into those chapters, Chapter 4 takes a look at XML DTDs
and schemas.
Understanding DTDs and CHAPTER
4
XML Schemas
IN THIS CHAPTER
• Why Use DTDs or Schemas? 90
• DTD Basics 93
• Determining when and when not to use quotes and other attributes can be confusing.
• DTD syntax does not follow or conform to the well-formed XML rules established in the
XML 1.0 specification.
• DTDs have very limited support for data typing.
• A DTD is very restrictive when defining the order of elements under a parent element.
• Because a given XML document can have only one external reference to a DTD, name-
space problems can arise.
• Programmatically accessing information contained within a DTD is too complex.
• DTDs have no built-in support for namespaces.
Even with their weaknesses, DTDs have wide industry support and are still are very important
in the world of XML. DTDs also have excellent support for entities. However, because of
some of the inherent weaknesses previously mentioned, an alternative to DTDs has been devel-
oped by the W3C. This alternative is termed “XML Schemas.”
Microsoft’s MSXML parser first began support for XML Data - Reduced schemas (XML-DR).
These schemas offer XML-like syntax, stronger data typing, and more flexibility in describing
an XML document’s structure and rules. Although Microsoft’s schema language differs from
the W3C’s XML schema, they both look and act a lot like XML, as you’ll soon see. Before
entering the world of DTDs and schemas, let’s take a look at when validation should and
should not be used when working with XML documents.
SCHEMAS
is that they are well formed, but because they do not contain any type of a DTD or schema ref-
erence, they are not validated. In addition to this, the XML parser built into Internet Explorer 5
or later does not validate XML documents by default, even if they do have a DTD associated
with them. Validation must be turned on programmatically.
With that in mind, it’s important to know that all XML documents do not have to be valid to
be considered useful documents. Many situations may occur where an XML document is
developed that will not be exchanged with internal or external parties and therefore doesn’t
need validation. As long as the creator of the document knows what it contains, any applica-
tions he or she writes will function.
In fact, validating a document can slow down the parsing process because the parser must
ensure that everything is kosher with the document’s structure. Validation also makes the
XML for ASP.NET Developers
92
document less flexible for future changes unless the DTD or schema is updated to reflect new
needs. As an example, assume that you create an application that gathers data from an XML
document. Rather than using a mechanism such as XPath to query elements by name, your
application recursively loops through the document and writes out the data it contains to a Web
page. Any new elements that are added cause no problems because they are included in the
looping process. Although this type of application admittedly may not have a lot of utility, it
definitely doesn’t require a DTD or schema to work correctly. Chapter 6, “Programming the
Document Object Model (DOM) with ASP.NET,” takes a look at a sample application that uses
XML to create a menu system very quickly on the server side. Because the document is itself
quite simple and needs to be accessed quickly, a DTD or schema will not be used—simply
because it isn’t necessary.
XML documents that do have an association with a DTD or schema do not always have to be
validated. Many parsers allow you to determine if validation should occur by telling the
parser to turn validation on or off. We’ll talk about this more in Chapter 5 (“Using the
XmlTextReader and XmlTextWriter Classes in ASP.NET”). All in all, the choice to validate is
yours alone because there’s nothing that dictates exactly when validation should be used. In
complex applications that expect particular elements and attributes, validation is more than
likely a good idea. In other applications, validation may be overkill. Use your best judgment
when deciding whether your document must be validated.
NOTE
As you go through the next few sections, remember that raw XML documents viewed
in Internet Explorer 5 or later are not validated. To check the validity of your docu-
ments or the samples that follow, point your browser to the following URL and click
the Demo link:
http://msdn.microsoft.com/Downloads/samples/
Internet/xml/xml_validator/sample.asp
If you’d rather work with a standalone product that validates, take a look at the fol-
lowing URLs:
http://www.xmlspy.com
http://www.xmetal.com
http://www.extensibility.com
An excellent source of XML-related products can also be found at
http://www.xmlsoftware.com.
Understanding DTDs and XML Schemas
93
CHAPTER 4
DTD Basics
As mentioned earlier, DTDs still have wide industry support even though they have several
weaknesses associated with them. Because of this support, a basic overview of DTDs is given
in this chapter so that you can understand and use them as appropriate in your XML docu-
ments. Because several books are available that concentrate specifically on DTDs, I won’t
spend a lot of time covering them here. However, you’ll learn enough to effectively use them
to validate XML documents. Because you are already familiar with what DTDs do, let’s jump
right into the specifics by first looking at a sample DTD. This sample is based on the XML
document shown in Listing 4.1.
UNDERSTANDING
19: <menuItem itemNumber=”b3”>
SCHEMAS
20: <hyperLink>/contact/default.aspx</hyperLink>
21: <name>Contact Numbers</name>
22: </menuItem>
23: </menuItem>
24: <!-- End menuItem Listings-->
25: </root>
<!DOCTYPE root [
<!ELEMENT root (menuItem+)>
<!ELEMENT menuItem (hyperLink+, name+, menuItem*)>
<!ATTLIST menuItem itemNumber ID #REQUIRED>
<!ELEMENT hyperLink ANY>
<!ELEMENT name (#PCDATA)>
]>
The DTD shown in Listing 4.2 is called an internal DTD. Internal DTDs actually “live” in an
XML document, rather than being found in an external document. Here’s the general syntax
for an internal DTD:
<!DOCTYPE rootElementName [ element, attribute, and entity declarations go here ]>
rootElementName would be substituted for the real name of the document’s root element. If
the root element name does not follow the DOCTYPE keyword, the parser will raise an error. As
shown in Listing 4.2, the root element was actually named root and therefore placed after the
DOCTYPE keyword.
An XML document referencing an external DTD (one that is in a separate file) could use one
of the following examples:
<!DOCTYPE rootElementName SYSTEM “http://www.someURL.com/pathToDTD/menu.dtd”>
This example is called an external SYSTEM DTD. It functions in a similar manner as includes do
in ASP.NET files or as .js or .css files do in HTML. After the parser sees the SYSTEM keyword,
it knows to look for an external DTD at the URI (Universal Resource Identifier) specified.
An external PUBLIC DTD would have the following syntax:
<!DOCTYPE rootElementName PUBLIC “-//SAMS//TEXT menus//EN” 4
“http://www.someURL.com/pathToDTD/menu.dtd”
An application coming across this definition may have a built-in way of knowing where to SCHEMAS
look for the public DTD based on the organization name (SAMS) and document name (menus).
If one cannot be found, the second URI (which is a SYSTEM URI) would be used.
DTD Elements
The DTD used in Listing 4.2 contains several element type declarations. Element declarations
are actually quite obvious in a DTD because they begin with the keyword ELEMENT. The struc-
ture of an element declaration is as follows:
<!ELEMENT elementName (content model)>
XML for ASP.NET Developers
96
Listing 4.2 declared the root, menuItem, hyperLink, and name elements (shown next):
<!ELEMENT root (menuItem+)>
<!ELEMENT menuItem (hyperLink+, name+, menuItem*)>
<!ELEMENT hyperLink ANY>
<!ELEMENT name (#PCDATA)>
Element declarations are fairly straightforward to implement. They start with <!ELEMENT and
are then followed by the element name. If the element contains any child elements, they are
listed in parentheses, as shown earlier with the menuItem element declaration. This listing of
the element’s content is called the content model. As for the menuItem element, its declaration
specifies that it can contain a hyperLink, name, and menuItem tag. Table 4.1 shows the mean-
ing of the other characters such as + and ? in the declaration.
Without these characters, elements such as hyperLink would be able to exist only once in the
document. By placing the + character after it, we are telling the parser that the hyperLink char-
acter may exist one or more times in the document. Using these characters is crucial to the
menuItem element. Because it can be a child of itself, you need the capability to have it option-
ally listed in the DTD declaration. When it isn’t found as a child of itself, as with the first
menuItem tag in Listing 4.2 (line 11), the DTD doesn’t cause an error, because the * character
follows it in the declaration and tells the parser that the menuItem tag may exist zero or more
times. After all this information is included, the ELEMENT declaration is ended with the >
character.
The content model for the hyperLink element declaration is ANY because it can contain text
content:
<hyperLink>/default.aspx</hyperLink>
or can be empty:
<hyperLink/>
If the hyperLink element were always empty, you could have declared it as follows:
<!ELEMENT hyperLink EMPTY>
Understanding DTDs and XML Schemas
97
CHAPTER 4
If the hyperLink element does not contain child elements (as in our example) and will contain
only parsed character data (PCDATA), its declaration can be coded as follows:
<!ELEMENT hyperLink (#PCDATA)>
Element declarations can also declare optional children. The following example specifies that
the menuItem element must contain either a hyperLink element or zero or one name elements:
<!ELEMENT menuItem (hyperLink|name?)>
In cases where an element may contain parsed character data or child elements, the following
declaration would be used:
<!ELEMENT menuItem (#PCDATA|hyperLink|name)*>
This type of declaration is called a mixed content model because the menuItem element can
contain text or elements. As a matter of definition, parsed character data is any data the parser
actually parses. Any data within a CDATA section (covered in Chapter 2) would be considered as
unparsed character data because the parser simply passes it through untouched.
DTD Attributes
The structure of a DTD attribute declaration follows this pattern:
<!ATTLIST elementName attributeName type defaultDeclaration>
Listing 4.2 contained one attribute declaration. This particular attribute was named itemNumber
and was an attribute of the menuItem element:
<!ATTLIST menuItem itemNumber ID #REQUIRED>
Attribute declarations start with <!ATTLIST followed by the element name that the attribute
applies to in the XML document. The element name is then followed by the actual name of the 4
attribute. In the case of the itemNumber attribute, it was declared having a type of ID, which
Attribute declarations can also have a default value. Up to this point, some of the examples
you’ve seen contained defaults such as #IMPLIED and #REQUIRED. Table 4.3 is a listing of the
default values that can appear within an attribute declaration.
SCHEMAS
“startValue”>
“value” Although the attribute is not required to have a value, the value assigned
in the attribute declaration will be the default. This is good for ensuring
that attributes contain values that you want to start with, as in the case of
enumerations.
Example: <!ATTLIST package invoiceID (yes|no) “yes”>
DTD Entities
Chapter 2 gave you your first taste of internal and external entities. These types of entities
function in a manner similar to an include file in ASP.NET. For example, the following general
XML for ASP.NET Developers
100
entity declaration defines companyName as an entity name. Whenever this entity is referenced in
an XML document, the companyName entity will be replaced with Tomorrow’s Learning:
<!ENTITY companyName “Tomorrow’s Learning”>
The discussion of parameter entities in Chapter 2 didn’t provide you with many specifics simply
because you had not learned about DTDs yet. So, we’ll cover these in more detail here.
A parameter entity functions in a similar way to the internal and external entities, although it is
referenced only in the DTD (in contrast to in the XML document) and uses slightly different
syntax. The following is an example of a parameter entity and how it could be used within a
DTD:
<!ENTITY % clubTypes “(TaylorMade|Cobra|TommyArmour|Titliest)”>
<!ELEMENT clubsUsed %clubTypes;>
<!ELEMENT clubPreference %clubTypes;>
This example declares a parameter entity named clubTypes and assigns it a value of
(TaylorMade|Cobra|Tommy Armour|Titliest). This value is then referenced in different ele-
ment declarations, including the clubsUsed and clubPreference elements. Doing this causes
the value (TaylorMade|Cobra|Tommy Armour|Titliest) to be dynamically placed into these
element declarations and makes maintenance of the declarations easier. If the clubsUsed and
clubPreference element declarations change to include different child elements, these can be
changed in one place (at the clubTypes entity declaration) rather than at each element declara-
tion. Although the preceding example is very simple, imagine if 20 different elements all
allowed similar types of child elements. Changing the element declarations in one place is
certainly much more efficient than editing each and every one of the element declarations
directly!
Parameter entities can also be quite useful when a base-set of attributes collectively describe
more than one element. For example, if attributes named caption, name, id, width, and
height are used on several elements, the following parameter entity definition could be used:
<!ENTITY % commonAttributes
“caption CDATA #IMPLIED
name CDATA #IMPLIED
id CDATA #REQUIRED
width CDATA #IMPLIED
height CDATA #IMPLIED”
>
Applying this entity to an element definition can be accomplished by doing the following:
<!ELEMENT object EMPTY>
<!ATTLIST object %commonAttributes;>
Understanding DTDs and XML Schemas
101
CHAPTER 4
A simple external DTD named golfer.dtd along with its XML document (golfer.xml) are
included with the book’s supplemental code. The external DTD uses a parameter entity named
clubTypes, as shown earlier.
DTD Notations
Notations are used within a DTD to specify external content that a parser cannot parse (binary
data, for example). Up until this point, everything we’ve worked with has been text based.
Notations allow for content that is non-ASCII to be included in an XML document. Because
parsers are not required to support binary data according to the XML 1.0 recommendation, a
notation allows binary data within a document to be associated with a helper application. In the
following example, a png notation has been declared and associated with Adobe Photoshop.
References to png will result in Photoshop being used to show the binary image data:
<!NOTATION png SYSTEM “photoshop.exe “>
<!ATTLIST picture format NOTATION (png) #IMPLIED>
Summing Up DTDs
DTDs provide structure for an XML document allowing it to be validated by a parser.
Although the DTD syntax is not pretty to look at and can be somewhat difficult to work with,
it will be some time before DTDs go away entirely because of their support throughout the
industry. You’ll see an example of using the System.Xml assembly to validate against a DTD
in Chapter 5. Before you learn how to do this type of validation, however, it’s important that
you see some new ways of providing structure and validation for XML documents. We’ll talk
about these “schemas” in the next section.
At the time this chapter was written, Microsoft’s System.Xml assembly supported the entire
XML schema specification, except for a few keywords that will be discussed later in this chap-
ter. In addition to its support for W3C XML schemas, the System.Xml assembly also provides
full support for Microsoft’s version of the XML schema (XML-DR) and DTDs.
The next few sections cover XML-DR as supported by .NET’s System.XML assembly. We will
also take a look at the W3C’s XML schema language toward the end of the chapter. To avoid
confusion between the different schema versions, the next few sections focus exclusively on
the XML-DR schema. Later on you’ll see that much of what you learn about Microsoft’s
XML-DR schema language will be applicable to the W3C’s version.
UNDERSTANDING
32: <ElementType name=”golfers” model=”closed” content=”eltOnly”>
SCHEMAS
33: <element type=”golfer” minOccurs=”1” maxOccurs=”*”/>
34: </ElementType>
35: <ElementType name=”lastName” model=”closed”
36: content=”textOnly” dt:type=”string”/>
37: <ElementType name=”firstName” model=”closed”
38: content=”textOnly” dt:type=”string”/>
39: <ElementType name=”name” model=”closed” content=”eltOnly” order=”seq”>
40: <element type=”firstName” minOccurs=”1” maxOccurs=”1”/>
41: <element type=”lastName” minOccurs=”1” maxOccurs=”1”/>
42: </ElementType>
43: </Schema>
XML for ASP.NET Developers
104
The XML schema shown in Listing 4.4 contains all the metadata about the golfers XML doc-
ument in Listing 4.3 and describes its structure using well-formed XML. Although the XML-DR
schema is certainly much more verbose than an equivalent DTD (shown in Listing 4.5), hope-
fully you’ll agree that it is much easier to read and understand even if you have never seen a
schema document before. After you learn the various element, attribute, and data type declara-
tions, you’ll find that creating schemas is a snap. In case you wanted to see how much code the
DTD would take for the same XML document, here it is:
Although the XML document in Listing 4.3 references an external schema (Listing 4.4.xdr),
the schema could have been included internally within the XML. To do this, you would simply
take out the schema namespace reference in the golfers root element and place the schema
directly under the golfer element:
<?xml version=”1.0” encoding=”UTF-8”?>
<golfers>
<Schema name=”golfers” xmlns=”urn:schemas-microsoft-com:xml-data”
xmlns:dt=”urn:schemas-microsoft-com:datatypes”>
<description>Golfers schema used to validate Listing4.3.xml</description>
<ElementType name=”course” model=”closed” content=”empty”>
<AttributeType name=”city” dt:type=”string” required=”yes”/>
<AttributeType name=”state” dt:type=”string” required=”yes”/>
Understanding DTDs and XML Schemas
105
CHAPTER 4
The golfer element calls the schema through using xmlns=”x-schema:#golfers”, where
#golfers is used to identify the schema name.
Now that you’ve had a chance to look at an XML schema, the next few sections will break it
apart and detail each piece used to create it.
WARNING SCHEMAS
Even slight changes to the namespaces will result in an error if validated by XML-
DR–aware parsers.
We’ll use the dt qualified namespace later in the schema to assign specified data types to dif-
ferent elements and attributes. The name attribute is optional, although its inclusion is defi-
nitely recommended for schema recognition purposes.
XML for ASP.NET Developers
106
Because XML-DR schemas look, act, and obey the XML rules just like regular XML docu-
ments, the beginning <Schema> tag must be ended appropriately using </Schema>. The XML-DR
Schema keyword is case sensitive and must appear with a capital S as shown.
The ElementType tag is case sensitive and must be used exactly as shown. The valid attributes
that can be used with the ElementType tag are listed in Table 4.4 along with their description.
Attribute Description
name The name attribute specifies the name of the element being declared. Names
used in the schema must follow XML element-naming rules.
Example: <ElementType name=”someName”....>
model The model attribute specifies how strictly or loosely content within an element
is managed. Values can include open or closed. A closed model restricts content
within the declared element to what the schema declares. Any attempts to add
content to the element will fail. An open model allows other content to be added
later.
Example: <ElementType model=”open”...>
content The content attribute is similar to the content model used within DTDs. It
determines what type of content can be included within the element. Valid val-
ues include empty, eltOnly, textOnly, and mixed. An element containing only
attributes would be considered empty (because it has no child elements or text),
whereas an element containing text and child elements would be considered
mixed. Elements that contain no text but that do contain child elements would
have a content value equal to eltOnly.
Example: <ElementType content=”textOnly”...>
Understanding DTDs and XML Schemas
107
CHAPTER 4
Attribute Description
dt:type You saw earlier that DTDs restrict element and attribute data types to string val-
ues (PCDATA and CDATA) for the most part, although there are a few other types
for attributes, such as ID and IDREF. XML schemas (both the XML-DR and
W3C XML schema versions) support a variety of data types. The dt prefix cor-
responds to the qualified namespace declared in the <Schema> tag. This is pre-
fixed to set this type attribute apart from another one that can be used with the
<element> tag (shown later in the chapter).
Example: <!ElementType dt:type=”int”...>
order One knock on DTDs is that elements specified in the content model for an ele-
ment declaration must appear in the order they appear. For example, the follow-
ing DTD declaration specifies that the golfer element must contain the name
and course elements in the order listed.
Example: <!ELEMENT golfer (name,course)>
If the golfer element contains a course element that comes before a name ele-
ment, an error will occur. XML-DR schemas avoid this by using the order
attribute. Valid values include one, many, or seq. Specifying one will allow any
element from a list to be included. Using many will allow elements in a list to
appear (or not appear) in any order, and using seq is like the preceding DTD
example—all elements must appear in a specific order.
Example:
<ElementType name=”golfer” model=”closed” content=”eltOnly”
➥order=”seq”>
<AttributeType name=”skill” dt:type=”string” required=”yes”/>
<AttributeType name=”handicap” dt:type=”i1” required=”yes”/> 4
<AttributeType name=”clubs” dt:type=”string” required=”yes”/>
SCHEMAS
<attribute type=”skill”/>
<attribute type=”handicap”/>
<attribute type=”clubs”/>
<attribute type=”id”/>
<element type=”name” minOccurs=”1” maxOccurs=”1”/>
<element type=”favoriteCourses” minOccurs=”1” maxOccurs=”1”/>
</ElementType>
The following golfer element declaration has a closed model (no additional items can be
added), which can contain elements but not text (eltOnly), and requires that the elements
XML for ASP.NET Developers
108
appear in the sequence listed (name followed by favoriteCourses). It also contains several
attribute declarations, which are covered shortly:
<ElementType name=”golfer” model=”closed” content=”eltOnly” order=”seq”>
<AttributeType name=”skill” dt:type=”string” required=”yes”/>
<AttributeType name=”handicap” dt:type=”i1” required=”yes”/>
<AttributeType name=”clubs” dt:type=”string” required=”yes”/>
<AttributeType name=”id” dt:type=”i2” required=”yes”/>
<attribute type=”skill”/>
<attribute type=”handicap”/>
<attribute type=”clubs”/>
<attribute type=”id”/>
<element type=”name” minOccurs=”1” maxOccurs=”1”/>
<element type=”favoriteCourses” minOccurs=”1” maxOccurs=”1”/>
</ElementType>
Organizing the golfer element declaration in this manner allows for content to be declared
once and then reused in other places rather than duplicating the information in multiple places.
As an example, the golfer element (and associated content) can be included as a child element
of the golfers root element declaration by doing the following:
<ElementType name=”golfers” model=”closed” content=”eltOnly”>
<element type=”golfer” minOccurs=”1” maxOccurs=”*”/>
</ElementType>
As you can see, after an element has been declared, it can be used within other elements. This
is accomplished by using the <element> tag. Going back to our earlier comparison with
ASP.NET, a declaration using the ElementType tag is similar to declaring a variable in an
ASP.NET page and then giving it a value. Using the element tag would be similar to assigning
the variable (or element) to another variable or object, which is the golfers element in this
case. The element tag has several attributes that can be used with it. These attributes, as well
as their designated purpose, are listed in Table 4.5.
Using the attributes with the element tag is as simple as including them within the tag. The
following code sample shows the course element being assigned to the favoriteCourses ele-
ment using the element tag:
<ElementType name=”favoriteCourses” model=”closed” content=”eltOnly”>
<element type=”course” minOccurs=”1” maxOccurs=”*”/>
</ElementType>
In this example, the course element must occur at least once (minOccurs=”1”). It can also
occur as many times as needed (maxOccurs=”*”).
If the favoriteCourses element needed to contain a more complex combination of elements,
how would you specify this in the schema? For example, assume that the favoriteCourses
element can contain two elements named courseDetails and course or one of three elements
named courseName, courseLocation, and courseLength. With a DTD, this is easily accom-
plished by using the pipe (|) and suffix characters, but nothing you’ve learned up to this point
allows you to do the same thing in a schema.
Implementing this variable combination of elements requires the group element as shown next:
<ElementType name=”favoriteCourses” model=”closed” content=”eltOnly” 4
order=”one”>
SCHEMAS
<element type=”courseDetails” minOccurs=”1” maxOccurs=”1”/>
<element type=”course” minOccurs=”1” maxOccurs=”1”/>
</group>
<group order=”one” minOccurs=”0” maxOccurs=”*”>
<element type=”courseName” minOccurs=”0” maxOccurs=”1”/>
<element type=”courseLocation” minOccurs=”0” maxOccurs=”1”/>
<element type=”courseLength” minOccurs=”0” maxOccurs=”1”/>
</group>
</ElementType>
In addition to the order attribute defined in Table 4.4, the group tag also accepts the
minOccurs and maxOccurs attributes referenced in Table 4.5.
XML for ASP.NET Developers
110
XML-DR Attributes
When Microsoft designed the XML-DR schema, they applied many of the same principles to
element and attribute declarations. This means that many of the features associated with ele-
ments also apply to the declaration and assignment of attributes:
<ElementType name=”golfer” model=”closed” content=”eltOnly” order=”seq”>
<AttributeType name=”skill” dt:type=”string” required=”yes”/>
<AttributeType name=”handicap” dt:type=”i1” required=”yes”/>
<AttributeType name=”clubs” dt:type=”string” required=”yes”/>
<AttributeType name=”id” dt:type=”i2” required=”yes”/>
<attribute type=”skill”/>
<attribute type=”handicap”/>
<attribute type=”clubs”/>
<attribute type=”id”/>
<element type=”name” minOccurs=”1” maxOccurs=”1”/>
<element type=”favoriteCourses” minOccurs=”1” maxOccurs=”1”/>
</ElementType>
This example declares four attributes named skill, handicap, clubs, and id, using the
AttributeType tag. It then assigns these to the golfer element using the attribute tag. It’s
important to note that the AttributeType elements declared have a scope limited to the golfer
element. To make them have a global scope, AttributeType elements would need to be moved
out on their own, becoming a child of the Schema element.
Attributes that can be associated with the AttributeType tag are shown in Table 4.6.
Attribute Purpose
name The name attribute specifies the name of the attribute being declared.
Although it may seem somewhat odd that the attribute you are declaring has
a name that is actually based on an attribute value, after you use the syntax a
few times, it will become comfortable to work with. Names used in the
schema must follow XML element-naming rules.
Example: <AttributeType name=”someName”....>
default default specifies a default value for the attribute.
Example: <AttributeType default=”1234”...>
required required determines whether a value is required for the attribute being
declared that will be used in an XML document. It accepts values of yes
and no.
Example: <AttributeType required=”yes”...>
Understanding DTDs and XML Schemas
111
CHAPTER 4
Attribute Purpose
dt:type This determines the data type for the attribute and functions the same as the
dt:type attribute used with the ElementType tag.
dt:values Although elements in a schema cannot be an enumeration data type, attrib-
utes can be. When dt:type=”enumeration” is specified on an attribute, the
enumerated values are specified using the dt:values attribute.
Example:
<AttributeType name=”clubs” dt:type=”enumeration”
dt:values=”TaylorMade Callaway Cobra”
>
After an attribute has been declared using the AttributeType element tag, it can be referenced
by an attribute element anywhere within an ElementType tag. Any attribute tags not con-
tained within an ElementType declaration will cause an error. The attribute element can
contain the attributes listed in Table 4.7.
SCHEMAS
declared on the AttributeType element. Possible values include yes and no.
Example: <attribute required=”no”...>
lists all the Microsoft XML-DR date types. These types are in addition to the primitive data
types already available within DTDs, such as PCDATA, ID, and so on.
Data types can be applied to elements or attributes in one of two ways. The first is to use the
dt:type attribute on the appropriate element or attribute declaration:
Because it is easier and involves less coding, the previous method of declaring the data type
within the AttributeType or ElementType elements is more common.
The description element can be used within an AttributeType, ElementType, Schema, group, SCHEMAS
element, or attribute tag to provide additional information about why things are being done
a certain way and can be useful for tracking revisions.
XML-DR Summary
XML-DR schemas provide a way to describe XML documents in a more flexible and human-
friendly manner than DTDs. Although these schemas generally require more information and
tags, reading XML-DR schemas is much easier than reading an equivalent DTD. Because an
XML-DR schema must be written according to standard XML rules, making changes to a
XML for ASP.NET Developers
114
schema via an XML parser is a very feasible proposition. Altering a DTD through the use of
an XML parser can prove to be very difficult, if not impossible, depending on the parser being
used.
With all the positive things XML-DR schemas have going for them, why did the W3C create a
new schema standard? To answer this completely, you’ll need to read through the next few sec-
tions. In short, the W3C XML schema specification provides additional functionality not avail-
able in the XML-DR schema.
To get you up to speed quickly with XML schemas, Listing 4.6 contains the XML-DR schema
you first saw in Listing 4.4, only it has now been converted to the W3C XML schema
specification.
At first glance, you may not see much resemblance to Microsoft’s XML-DR schemas.
However, many features are found in Microsoft’s version that are very similar in the W3C’s 4
version. The following sections explain a few differences and explain some new concepts
or
<element name=”course” type=”string” minOccurs=”1” maxOccurs=”unbounded”/>
An element or attribute that is applied to another element can then refer to this declaration
through using a ref attribute:
<attribute ref=”city”/>
<element ref=”course”/>
Eliminating the ElementType and AttributeType elements by combining them into the ele-
ment and attribute elements makes a lot of sense and should make working with schemas
less confusing.
This example restricts the custom simpleType to be a positiveInteger data type but also
extends it through the use of the minInclusive and maxInclusive XML schema elements.
After it is specified, the simpleType can be used to define the data type of an element or
attribute:
<element name=”score” type=”myNamespace:roundScore” minOccurs=”1”
➥maxOccurs=”unbounded”/>
anytype
string decimal
normalizedString integer
FIGURE 4.1
Diagram of XML schema data types.
4
In cases where more complex restrictions need to be added to a base data type, the pattern
element can be used. This element has a value attribute that can accept regular expressions.
The following example defines a simpleType named us-zipcode:
<simpleType name=’us-zipcode’>
<restriction base=’string’>
<pattern value=’[0-9]{5}(-[0-9]{4})?’/>
</restriction>
</simpleType>
The simpleType can also be constructed and associated directly within an element or attribute.
However, declaring a simpleType in this manner restricts the scope of the type to be local to
the element or attribute it is declared within. This is compared to the global scope associated
with a simpleType declared on its own, as a child element of the schema element. An example
of a local declaration follows. As mentioned, this declaration is very restricting because it can
be used only within the element named score (it cannot be used anywhere else in the docu-
ment). However, if the simpleType applies only to this one element, it may make sense to
declare it this way:
<element name=”score”>
<simpleType>
<restriction base=”positiveInteger”>
<minInclusive value=”55”/>
<maxInclusive value=”100”/>
</restriction>
</simpleType>
</element>
XML-DR schemas attempt to emulate some of the simple and complex typing through their
use of the ElementType and AttributeType elements. However, the W3C XML schema offers
a more powerful solution. The following is an example of using a complexType element to
encapsulate different elements and attributes:
<complexType name=”golferType” mixed=”false”>
<sequence>
<element name=”name” type=”myNamespace:nameType”/>
<element name=”favoriteCourses”
type=”myNamespace:favoriteCoursesType”/>
</sequence>
<attribute name=”skill” type=”string” use=”required”/>
<attribute name=”handicap” type=”int” use=”required”/>
<attribute name=”clubs” type=”string” use=”required”/>
<attribute name=”id” type=”int” use=”required”/>
</complexType>
The sequence element type lets the parser know that the name and favoriteCourses elements
must appear in the order shown in the schema: name first, followed by favoriteCourses.
If you had wanted to allow one of three elements to potentially appear within an XML docu-
ment, the sequence element could be replaced by the choice element:
<complexType name=”golferType” mixed=”false”>
<choice>
<element name=”name” type=”nameType”/>
<element name=”favoriteCourses” type=”myNamespace:
➥favoriteCoursesType”/>
<element name=”courseDifficulty” type=”myNamespace:
➥courseDifficultyType”/>
</choice>
<attribute name=”skill” type=”string” use=”required”/> 4
UNDERSTANDING
<attribute name=”clubs” type=”string” use=”required”/>
SCHEMAS
<attribute name=”id” type=”int” use=”required”/>
</complexType>
Use of the choice element will result in one of the nested elements being listed. In this exam-
ple, the name, favoriteCourses, or courseDifficulty elements can be included within an
XML document, but only one can exist. If more than one element is used, an error will occur
when the XML document is validated against the schema.
At times, you may want three different elements to appear in a complexType but don’t care if
they appear in an unordered manner. For instance, the code shown next requires that the name,
favoritecourse, and courseDifficulty elements all appear within the XML document, but it
does not restrict their sequence as the sequence element did earlier:
XML for ASP.NET Developers
122
It’s important to note that the all element can appear only directly after a complex type decla-
ration. This means that it cannot be nested within another particle such as the sequence ele-
ment. This is contrary to how the sequence and choice elements can be combined to add
additional logic:
<complexType name=”golferType” mixed=”false”>
<choice>
<element name=”name” type=”nameType”/>
<sequence>
<element name=”favoriteCourses”
➥type=”myNamespace:favoriteCoursesType”/>
<element name=”courseDifficulty”
➥type=”myNamespace:courseDifficultyType”/>
</sequence>
</choice>
</complexType>
The preceding example states that either the name element can be appear within an XML docu-
ment, or a sequence consisting of the favoriteCourses and courseDifficulty elements. By
combining choice and sequence elements, some interesting options can be created. Also note
that the group element found in the XML-DR schemas is also available to use in the W3C
XML schema version in a similar fashion.
After the specifics associated with a complex type have been defined, it can then be referenced
by name (golferType was used in the previous examples) within another complex type or
within an element by using code similar to the following:
<element name=”golfer” type=”myNamespace:golferType” minOccurs=”1”
➥maxOccurs=”unbounded”/>
Understanding DTDs and XML Schemas
123
CHAPTER 4
After complex types have been declared and defined, they can be inherited and extended by
other complex types. This is a very powerful feature that allows for reusability resulting in less
coding where used appropriately. Taking a break from golfing for a minute and using an
animal complexType as an example, as shown in Listing 4.7, a feline and canine type can
extend animal using the complexContent and extension elements:
Listing 4.8 shows the XML document that the previous XML schema describes. Notice that
the cat element lists child elements, including the furColor, legCount, weight, and meows
elements. This is because the cat element must include all the elements found within the
feline complex type, which is an extension of the animal complex type.
Customers ∞ Orders
CustomerID OrderID
CompanyName CustomerID
ContactName EmployeeID
ContactTitle OrderDate
Address RequiredDate
City ShippedDate
Region ShipVia
PostalCode Freight
Country ShipName
Phone ShipAddress
Fax ShipCity
ShipRegion
ShipPostalCode
ShipCountry
FIGURE 4.2
Relating the Customers and Orders Tables in the Northwind Database.
Listing 4.9. An XML Schema Representing the Customers and Orders Tables in the
Northwind Database
1: <?xml version=”1.0” encoding=”UTF-8” ?>
2: <xsd:schema id=”northwind” targetNamespace=”http://www.northwind.com”
3: xmlns=”http://www.northwind.com”
4: xmlns:xsd=”http://www.w3.org/2001/XMLSchema”>
5: <xsd:annotation>
6: <xsd:documentation xml:lang=”en”>
7: This Schema validates against northwind.xml
8: </xsd:documentation> 4
9: </xsd:annotation>
SCHEMAS
11: <xsd:complexType>
12: <xsd:sequence>
13: <xsd:element ref=”CustomersTable” maxOccurs=”unbounded” />
14: <xsd:element ref=”OrdersTable” maxOccurs=”unbounded” />
15: </xsd:sequence>
16: </xsd:complexType>
17: </xsd:element>
18: <xsd:element name=”CustomersTable”>
19: <xsd:complexType>
20: <xsd:sequence>
21: <xsd:element name=”CustomerID” type=”xsd:string” />
XML for ASP.NET Developers
126
This particular XML schema leverages the unique and keyref elements (shown in bold) to
establish relationships. Lines 44–47 establish the CustomerID element (a child element of the
CustomersTable element) as being unique and names this constraint CustomerID_PK. This is
done by assigning the selector element xpath attribute to a period (.). Thinking back to
4
Chapter 3, you’ll remember that the period (.) represents the context node. In this case,
SCHEMAS
selector element. The field element’s xpath attribute is then assigned to the CustomerID
element. In cases where more than one element or attribute makes up a unique constraint, mul-
tiple field elements can be listed as children of the unique element, the same as multiple
fields can be combined to create a primary key in a relational database. Had this example con-
tained an attribute that must hold a unique value, the xpath attribute of the field element
could have listed the path to the attribute instead (for example: @attributeName).
After the unique constraint named CustomerID_PK is established, the OrdersTable element can
then be related to the CustomersTable element in a primary/foreign key type relationship.
Lines 84–88 show how the keyref element can be used to refer to an existing constraint,
CustomerID_PK in this case. After the constraint is selected, the selector and field elements
can be determined by using xpath statements as detailed in the previous paragraph. In cases
XML for ASP.NET Developers
128
where an element or attribute doesn’t have to be unique but a key needs to be established, the
key element can be used in place of the unique element.
</xsd:schema>
This will associate all top-level components (those that are immediate children of the
xsd:schema element) with the namespace URI of http://www.someURI.com. This does not
associate any other elements or attributes that are not immediate children of the xsd:schema
Understanding DTDs and XML Schemas
129
CHAPTER 4
element with the namespace! This is very important to understand. In cases where other ele-
ments or attributes within the schema need to be associated with the namespace as well, the
elementFormDefault and attributeFormDefault attributes can be applied to the schema root
element. The following example shows how all elements not associated with the XML schema
namespace can be associated with the namespace URI contained within the targetNamespace
attribute:
<?xml version=”1.0” encoding=”UTF-8” ?>
<xsd:schema xmlns:xsd=”http://www.w3.org/2001/XMLSchema”
targetNamespace=”http://www.someURI.com” elementFormDefault=”qualified”
>
</xsd:schema>
In cases where some element declarations should not be associated with the target namespace,
they can remain unqualified by adding the form attribute to the element declaration:
<?xml version=”1.0” encoding=”UTF-8” ?>
<xsd:schema xmlns:xsd=”http://www.w3.org/2001/XMLSchema”
targetNamespace=”http://www.someURI.com” elementFormDefault=”qualified”
>
</xsd:schema>
SCHEMAS
namespace declarations that are applied to the root element of the XML document. You saw
a simple example of referencing an XML schema that did not have a targetNamespace
attribute included within its schema root element back in Listing 4.8. This example used the
noNamespaceSchemaLocation attribute along with the schema instance namespace (xsi) to
reference the schema named Listing4.7.xsd:
<?xml version=”1.0” encoding=”UTF-8”?>
<cat xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:noNamespaceSchemaLocation=”Listing4.7.xsd”
>
<furColor>Black</furColor>
<legCount>4</legCount>
XML for ASP.NET Developers
130
<weight>3</weight>
<meows>true</meows>
</cat>
Both examples assume that the XML schema document is located within the same directory as
the XML document. A file path or URL can be used in cases where the two documents are in
separate locations.
Summary
This chapter provided you with an in-depth look at DTDs, XML Data–Reduced schemas
(XML-DR), and W3C XML schemas. Although DTDs have their pitfalls, they are still very
prominent and will be until an implementation of the XML schema recommendation is built in
to more parsers and supported throughout different industries.
Because ASP.NET relies on the System.Xml assembly for XML parsing, you can avoid many
of the shortcomings associated with DTDs by validating your XML documents using XML-DR
schemas or the new W3C XML schemas because of the superior support for data types, XML-
like structure, and overall power that schemas have to offer. Keep in mind that although you
Understanding DTDs and XML Schemas
131
CHAPTER 4
can certainly use XML-DR schemas for validation, it is recommended that you switch to the
W3C XML schema specification since Microsoft provides full support for the schema stan-
dards. If you already have a lot of time invested in XML-DR schemas, a few XML software
packages (XMLSpy, for instance) are available that already convert between the XML-DR and
W3C XML schemas, making it relatively easy to make the switch in the future.
The next chapter explores the XmlTextReader, XmlValidatingReader, and XmlTextWriter
classes and shows how the information you’ve learned up to this point can be used in real
applications to validate XML documents. Stay along for the ride because things from here on
out promise to be a little more hands-on and exciting!
5
XmlTextWriter Classes in
ASP.NET
IN THIS CHAPTER
• Introducing the System.Xml Assembly 134
• XmlDocument
• XmlElement
• XmlNode
• XmlNamedNodeMap
• XmlNodeList
Using the XmlTextReader and XmlTextWriter Classes in ASP.NET
135
CHAPTER 5
• XmlNodeReader
• XmlTextReader
• XmlTextWriter
• XmlValidatingReader
• XPathDocument
• XslTransform
You’ll see how to use these classes and many others in upcoming chapters. In this chapter we
will focus specifically on the XmlTextReader, XmlTextWriter, and XmlValidatingReader
classes along with several of their supporting classes. Figure 5.1 gives an overview of how
these classes fit together.
XML 1.0
Abstract XmlReader XmlWriter Abstract
Namespaces
Support Classes
XmlConvert
XmlResolver
FIGURE 5.1
XmlReader and XmlWriter architecture.
when and why the classes should be used. In Chapter 6, “Programming the Document Object
Model (DOM) with ASP.NET,” you’ll learn how to use the Document Object Model(DOM) (a
AND
tree-based Application Program Interface(API)) to access specific nodes within an XML docu-
ment in a variety of ways, ranging from programmatic access to using XPath statements. To
XML for ASP.NET Developers
136
see when and why you should use the XmlTextReader and XmlTextWriter classes discussed in
this chapter, it’s necessary to provide you with a basic overview of how the DOM works.
The concept of the DOM is actually very simple. If you think of a genealogical chart contain-
ing your family lineage, you’ll recognize that it contains a tree structure with hierarchical rela-
tionships in it. For example, if you start with your grandfather in the chart, your mother may be
listed under him. You would, of course, be listed under your mother.
The DOM works in a manner similar to the genealogical chart just described. Each node found
within an XML document is loaded into memory so that a tree hierarchy is constructed with
the root node being the start of the hierarchy. By having this tree in memory, the capability to
update, delete, and insert nodes is made available through a specific API. Although the pro-
gramming API exposed by the DOM is very powerful, it can present problems with memory
consumption, especially when large XML documents must be parsed. For example, if you have
an XML document that is several megabytes in size and each node is loaded into the DOM tree
structure and placed into memory, you can imagine how this could tax a computer in cases
where multiple XML documents of this size are being processed simultaneously.
To overcome some of the potential memory problems associated with using the DOM (the
DOM is very useful in some situations, as you’ll see in Chapter 6) and to provide an alternative
in cases where the DOM isn’t necessary, the .NET platform includesthe XmlTextReader class.
This class “lives” within the System.Xml assembly. Rather than loading the entire document
into memory as the DOM does, the reader processes one node at a time in a forward-only man-
ner. This is possible because the XmlTextReader is a stream that parses the XML document
into a sequence of XML tokens. Information about these tokens can be obtained through call-
ing different properties and methods built in to the class.
This forward-only stream creation means that when an XML document’s root element is
reached, only one node is in memory at any given time. When a child node is reached, its
ancestors have been parsed, but they are not in memory and going back to them is not an
option (remember, the process is forward only). This type of functionality allows one XML
token at a time to be checked and processed rather than loading everything into memory as
with the DOM. It provides a very low memory alternative that is capable of working with large
XML documents efficiently.
The XmlTextReader functions by starting with an 8KB buffer that is capable of growing to the
size it needs to accommodate the largest token in the stream. For example, a 32KB comment
has to be returned as a single, contiguous token, which causes the parser buffer to grow to
32KB in this case. If that is the biggest token that is added to the stream, the buffer will stay
that size for the rest of the document.
Using the XmlTextReader and XmlTextWriter Classes in ASP.NET
137
CHAPTER 5
All attributes are buffered as well because of namespace support. A start tag with 100 attributes
will typically require a larger buffer as a result. Even though the buffer size grows to fit the
largest token, the XmlTextReader still offers a very memory-conservative approach to dealing
with XML documents[md]especially when compared to the DOM. So is the DOM bad?
Certainly not! It is very useful in many situations, as you’ll see in Chapter 6.
This version of the constructor allows the file path (a string) to the XML document to be
passed as an argument. Several other constructors can be used while instantiating the
XmlTextReader as well. For example, a TextReader or Stream can also be passed in as argu-
ments. See the .NET SDK for more details on the different XmlTextReader constructors.
After the XmlTextReader object is created, parsing can then begin on the document in a for-
ward-only manner. This is accomplished by using the different properties and methods built in
to the class. Table 5.2 lists what properties are available and Table 5.3 lists the available
methods.
Using the XmlTextReader and XmlTextWriter Classes in ASP.NET
139
CHAPTER 5
Property Description
AttributeCount Returns the number of attributes on the current node. This can be
used in looping through the attributes associated with a particular
element node. The MoveToNextAttribute() method (see Table
5.3) provides a more efficient manner of doing this, however.
BaseURI Returns the base URI of the current node.
CanResolveEntity Returns a Boolean value based on whether an entity is resolved.
Depth Returns the depth of the current node in the XML element stack.
Encoding Returns the encoding attribute for the XML document being
parsed.
EOF Returns a Boolean value telling whether the XmlTextReader is
positioned at the end of the stream.
HasAttributes Returns a Boolean value indicating whether the current node has
any attributes.
HasValue Returns a Boolean value indicating whether the node can have a
value. Although many nodes can have values, such as attributes
and CDATA sections, others cannot, such as end element tags.
IsDefault Returns a Boolean value that specifies whether the current node
is an attribute that was created from the default value defined in
the DTD or schema.
IsEmptyElement Returns a Boolean value indicating whether the current node is
an empty element, such as (<FavoriteCourses/> for example).
Item Returns the value of the attribute when looping through a collec-
tion. This is the indexer for the XmlTextReader in C#.
LineNumber Returns the current line number of the position within the XML
document. This is similar to the getLineNumber() method asso-
ciated with the Locator class in SAX.
LinePosition Returns the current line position. This is similar to the
getColumnNumber() method in SAX.
LocalName Returns the name of the current node without the namespace pre-
fix attached to it.
Name Returns the name of the current node along with any associated 5
namespace prefix.
XMLTEXTREADER
XMLTEXTWRITER
Property Description
NamespaceURI Returns the namespace URI of the node the reader is positioned
on.
NameTable Gets the XmlNameTable associated with this implementation.
Using the NameTable can result in more efficient object compar-
isons. An example of using it is shown later in the chapter.
NodeType You’ll likely use this property very heavily in your ASP.NET
applications. It returns the type of the current node based on the
XmlNodeType enumeration.
Normalization Gets or sets a value indicating whether to do whitespace normal-
ization as specified in the WC3 XML recommendation version
1.0 (see http://www.w3.org/TR/1998/REC-xml-19980210).
Prefix Returns the namespace prefix (if any) of the current node.
QuoteChar Returns the quotation mark character used to enclose the value of
an attribute node. For example, ‘ or “.
ReadState Returns the read state of the stream based upon the ReadState
enumeration. Possible values can be ReadState.Closed,
ReadState.EndOfFile, ReadState.Error, ReadState.Initial,
or ReadState.Interactive.
Value Returns the text value of the current node.
WhitespaceHandling Gets or sets a value that specifies how whitespace is handled.
This property ties into the WhitespaceHandling enumeration.
XmlLang Returns the current xml:lang scope where the current node
resides.
XmlResolver Gets or sets the XmlResolver used for resolving URLs and/or
PUBLIC IDs used in external DTDs. The XmlUrlResolver class
inherits from the XmlResolver abstract class and is used to
resolve DTD and schema locations. If the directory where the
DTD or schema resides requires authentication, the credentials to
pass for authentication can be set through this property.
XmlSpace Returns the current xml:space scope where the current node
resides. This property is associated with the XmlSpace enumera-
tion that contains values of XmlSpace.None, XmlSpace.Default,
XmlSpace.Preserve.
Using the XmlTextReader and XmlTextWriter Classes in ASP.NET
141
CHAPTER 5
Method Description
Close() Changes the ReadState to Closed and sets all the properties
back to zero. Use in conjunction with the ReadState property.
GetAttribute() Returns the value of an attribute.
GetRemainder() Returns the remainder of the buffered XML in cases where a
different XmlTextReader may finish reading a document. After
this method is called, the EOF property is set to true.
IsStartElement() Returns true if the current content node is a start element tag.
LookupNamespace() Resolves a namespace prefix that exists within the current ele-
ment’s scope. Assuming the namespace resolves, the namespace
URI will be return in a string value.
MoveToAttribute() Move to a specified attribute based on indexed position or
name.
After calling this method, you can access the Name,
NamespaceURI, and Prefix properties associated with that
attribute.
MoveToContent() Checks whether the current node is a content (nonwhitespace
text, CDATA, Element, EndElement, EntityReference, or
EndEntity) node. If the node is not a content node, the method
skips ahead to the next content node or end of file. Nodes that
are of type ProcessingInstruction, DocumentType, Comment,
Whitespace, or SignificantWhitespace will be skipped over.
MoveToElement() After navigating through attributes of an element, you can
move back to the element by using this method.
MoveToFirstAttribute() Moves to the first attribute of an element node.
MoveToNextAttribute() Moves to the next attribute of an element node. This can be
used effectively in a while loop to enumerate through any ele-
ment attributes.
Read() Reads the next XML node from the stream. You will normally
use this method within a while loop to parse through an XML
document.
ReadAttributeValue() Reads and parses the attribute value into Text and/or
EntityReference node types. 5
XMLTEXTREADER
XMLTEXTWRITER
Method Description
ReadChars Reads the text contents of an element into a character buffer.
The size of the buffer will normally be set initially. Characters
are then added to the buffer:
int len;
while ((len = reader.ReadChars(buffer, 0, buflen)) > 0) {
//Call Buffer Function or create String
}
Listing 5.1 provides an example of working with some of the XmlTextReader properties and
methods to walk through an XML document and write out the XML tokens it finds to the
browser. An explanation of the code follows.
47: }
48: private string indent(int number) {
49: string spaces = “”;
AND
This example starts by referencing the System.Xml assembly in line 1. Doing this allows
access to all the classes found within the assembly, including the XmlTextReader class needed
for this application. After the page is hit, the Page_Load() event takes care of instantiating the
ReadXmlFile class (line 58). After the class is instantiated, the ReadDoc() method is called and
the path to the XML document is passed in as an argument (line 60). The ReadDoc() method
takes care of instantiating the reader by passing the file path to its constructor. It then passes
the newly created XmlTextReader object to the WriteXml() (line 22) method. Each node in the
XML document is processed as it is reached. This processing begins with a call to the
XmlTextReader’s Read() method (line 23). As each token in the stream is read, the node type
is checked. If the node type is equal to XmlNodeType.Element, the MoveToNextAttribute()
method is called within a while loop (lines 27–30). Any existing attributes are iterated through
and displayed, as appropriate.
If the node is not an element but is instead an element end tag (XmlNodeType.EndElement), the
appropriate closing tag is included (line 38). If the node is actually a text node
(XmlNodeType.Text), the text is included, but the font color is set to red so that the data stands
out more (lines 41–44). The text is read by calling the Value property.
Using the XmlTextReader and XmlTextWriter Classes in ASP.NET
145
CHAPTER 5
Each node is indented a specified amount by checking the XmlTextReader’s Depth property.
This property automatically tracks how deep you are into an XML document’s hierarchical
structure, making it very easy to display the document in a properly formatted manner or sim-
ply track parent/child relationships. After the entire document has been parsed, it is written out
to the browser.
Note that the attributes are not automatically passed to you, as with SAX. If you didn’t want to
work with any attributes, they could simply be skipped over. The same logic follows for text
nodes, processing instructions, comments, and so on. Until you tell the code to pull these dif-
ferent node types from the stream, they are simply ignored, which keeps buffer sizes minimal
and results in very fast and efficient XML document parsing.
The XmlTextReader can also be associated with the XmlNameTable class to increase efficiency
even more in certain scenarios. The XmlNameTable class is capable of holding a table of atom-
ized string values and is useful when the same element, namespace, or attribute name is com-
pared multiple times while reading the XML document stream. Instead of doing costly string
comparisons, the XmlNameTable can do object pointer comparisons. For example, if a condi-
tional check is done on a particular namespace URI many times while parsing, the URI can be
added to the XmlNameTable as shown next to increase efficiency:
XmlTextReader reader = new XmlTextReader(url);
object myUri = reader.NameTable.Add(“http://www.SomeServer.com/namespaceURI”);
As the stream of XML tokens is read, a direct object pointer comparison can then be made
against the XmlNameTable object:
if (reader.NamespaceURI.Equals(myUri)) {
//Do special processing if the two URI values are equal
}
This method of testing for a URI value is in contrast to the following less-efficient way of
doing the same conditional test:
if (reader.NamespaceURI == “http://www.SomeServer.com/namespaceURI”) {
//Do special processing if the two URI values are equal
}
model but that the reverse was not true. This is possible because with a pull model you deter-
AND
mine what should be pulled and when it should be pulled. With a push model you have no con-
trol over what you get because everything found in the document is pushed to you via events.
XML for ASP.NET Developers
146
Before jumping into the code used to create the SAX parser, you may wonder why you would
want to create this in the first place, especially because the pull model exposed by the
XmlTextReader is so efficient already.
The first answer to this question is that SAX is very popular throughout the world. Because of
its popularity, many applications being ported to .NET may have SAX ContentHandlers with
a lot of code in them that functions well. Converting all this code to work directly with the
XmlTextReader could be costly from a management perspective. Rather than rewriting these
handlers, the code that will be shown could be used temporarily while other parts of the appli-
cation are ported to .NET.
If you don’t buy the previous reason for creating the SAX parser class in .NET (hey, I gave it
my best shot!), suffice it to say that creating a SAX parser provides an excellent stage for dis-
playing just how powerful the XmlTextReader can be in .NET applications. Through seeing the
process of creating the SAX parser, you’ll become more familiar with different ways that the
XmlTextReader can be used and see a few other things you can do with .NET classes.
If you’ve used SAX in the past, you’re probably intimately familiar with the different parts of
the SAX parser ContentHandler interface. Listing 5.2 shows this SAX interface converted to a
.NET interface.
If you’re not familiar with this interface, it’s actually quite simple to understand. The different
interface members are called by the SAX parser as it parses an XML document. For example,
when the document parsing starts, startDocument() is called. When an element type node is
found within the document, startElement() is found and any attributes associated with the
element are passed as arguments (whether you want them or not). This push process continues
until the end of the document is reached (endDocument()).
A few classes are required by SAX that are not built in to the .NET platform. Fortunately,
creating custom classes to emulate the classes that the SAX ContentHandler expects is very
straightforward. Listing 5.3 shows some helper classes and a struct that were created to
enable SAX-like functionality in the IContentHandler interface (and in the IErrorHandler
interface, which is not shown here). More specifically, the code takes care of creating a
SAXParseException, Attributes, and Locator class. The Attributes class contains a collec-
tion of attributes that will be passed as an argument to startElement() in the ContentHandler.
The Locator class allows the line position and column position of the SAX parser to be
tracked as it is parsing the XML document.
16: }
XML for ASP.NET Developers
148
After all the classes expected by the SAX ContentHandler and ErrorHandler interfaces are
created and ready to be used, the actual implementation of the SAX parser can be constructed
through using the XmlTextReader class, along with a few others. Listing 5.4 shows this imple-
mentation and is followed by step-by-step details on what the code is doing.
Listing 5.4 Constructing a SAX Parser Using the XmlTextReader Class (SAXParser.cs)
1: namespace XmlParsers.Sax {
2: using System;
3: using System.Xml;
4: using System.Collections;
5: using XmlParsers.Sax.Handlers;
6: using XmlParsers.Sax.Helpers;
7:
8:
/// <summary>
/// The SaxParser class build a SAX push model
5
XMLTEXTREADER
XMLTEXTWRITER
98: break;
99: case XmlNodeType.Whitespace:
AND
XML for ASP.NET Developers
152
Let’s take a step-by-step look at what is happening in the code shown in Listing 5.4.
XmlNameTable. This is because object comparisons can be done during the parsing process that
offer performance benefits over regular string comparisons. In Step 6, you’ll learn more about
AND
28: try {
29: reader = new XmlTextReader(url);
30: object nsuri = reader.NameTable.Add(
31: “http://www.w3.org/2000/xmlns/”);
adding each attribute’s Name, NamespaceURI, and Value to the Attributes object collection.
Notice that as the attributes are being enumerated through, the NamespaceURI property of the
reader object is being compared to the XmlNameTable object named nsuri (line 44). As men-
tioned earlier, this object-to-object comparison results in performance gains over simply com-
paring strings, as shown next:
if (reader.NamespaceURI == “http://www.w3.org/2000/xmlns/”) {
........
}
After all attributes has been enumerated through (assuming some existed in the first place), the
XmlTextReader’s MoveToElement() method is called to get back on the original element. Now
that the necessary information about the element has been gathered, the ContentHandler’s
startElement() method is called and the objects detailed earlier along with information about
the element itself are passed in as arguments (lines 63–65). After this is completed, a check is
made to see whether the element’s content model is empty (line 66). If it is, the endElement()
method is called to let the ContentHandler know that the parser will be moving on to other
nodes within the XML document.
40: case XmlNodeType.Element:
41: nsstack.Push(null);//marker
42: atts = new Attributes();
43: while (reader.MoveToNextAttribute()) {
44: if (reader.NamespaceURI.Equals(nsuri)) {
45: prefix = “”;
46: if (reader.Prefix == “xmlns”) {
47: prefix = reader.LocalName;
48: }
49: nsstack.Push(prefix);
50: Handler.startPrefixMapping(prefix,
51: reader.Value);
52: } else {
53: SaxAttribute newAtt = [sr]
54: new SaxAttribute();
55: newAtt.Name = reader.Name;
56: newAtt.NamespaceURI = [sr]
57: reader.NamespaceURI;
58: newAtt.Value = reader.Value;
59: atts.attArray.Add(newAtt);
60: } 5
61: }
XMLTEXTREADER
XMLTEXTWRITER
62: reader.MoveToElement();
63: Handler.startElement(reader.NamespaceURI,
AND
66: if (reader.IsEmptyElement) {
67: Handler.endElement(reader.NamespaceURI,
68: reader.LocalName, reader.Name);
69: }
70: break;
is called on the ContentHandler (line 109). If an error arises during the parsing process, such
as the XML document not being found or not being well-formed, the error will be caught by
AND
XML for ASP.NET Developers
158
the catch block shown in lines 111–117. This block takes care of calling the ErrorHandler
object so that the error can be reported to the SAX application.
After the parsing process has completed, the finally block will be called. This block takes
care of ensuring that the XmlTextReader is not already closed through checking the ReadState
property.
109: Handler.endDocument();
110: } //try
111: catch (Exception exception) {
112: saxException.LineNumber = reader.LineNumber.ToString();
113: saxException.SystemID = “”;
114: saxException.Message =
115: exception.GetBaseException().ToString();
116: errorHandler.error(saxException);
117: }
118: finally {
119: if (reader.ReadState != ReadState.Closed) {
120: reader.Close();
121: }
122: }
The ContentHandler class in this example receives data about different nodes in the XML
document and then writes the results out to the browser. However, the same code (with minor
modifications) could be used to update a database, text file, or other data store. In concluding
the discussion on creating a .NET version of the SAX parser by using the XmlTextReader
class, it’s important to keep in mind that it is always recommended that you use the
XmlTextReader directly in your ASP.NET applications. It is engineered to provide fast and
efficient support of XML document parsing.
methods found in the XmlTextReader are also found in the XmlValidatingReader. However,
the XmlValidatingReader is designed specifically to validate XML documents or read XML
AND
The XML document that is parsed by the reader object (an XmlTextReader object) is used by
the XmlValidatingReader as it does comparisons of the XML document against DTDs or
schemas.
The Add() method has several overloaded forms that can be used. The preceding version
accepts the namespace associated with the schema (normally the targetNamespace URI for
XSD schemas) and the name of the schema document. The Add() method can be called multi-
ple times in cases where several schemas are used in an application for validation. The main
benefit of using this class to hold different schemas is that the schemas do not have to be
reloaded each time validation needs to take place because they can be pulled from the schema
cache.
After adding the appropriate schemas to the collection, the XmlValidatingReader (named
vReader in this example) needs to be associated with the collection to take advantage of its
caching features:
vReader.Schemas.Add(schemaCol);
validation process. In .NET, event handlers can be attached to an object by using += and sub-
tracted by using the -= syntax. The XmlValidatingReader has one event named Validation
AND
EventHandler that can be attached to catch errors that may arise. Attaching to the event
XML for ASP.NET Developers
162
involves using the += syntax along with specifying the callback handler that should be called if
a validation error occurs. After the event handler is attached, the Read() method is called on
the XmlValidatingReader object to start validating the XML document.
vReader.ValidationEventHandler +=
new ValidationEventHandler(this.ValidationCallBack);
// Parse through XML
while (vReader.Read()){}
The callback handler (named ValidationCallBack()) is shown in the following code segment.
Note that an external class with specific error-handling capabilities could be referenced as well.
private void ValidationCallBack(object sender, ValidationEventArgs args) {
//Deal with any errors here
}
65: writer =
66: new StreamWriter(_logFile,true,Encoding.ASCII);
XML for ASP.NET Developers
164
The Validator class simply implements the functionality discussed in the last few sections.
The bulk of the work is done in the Validate() method, which accepts the following
parameters:
The Validate() method first sets the values of a few global variables and then instantiates the
XmlTextReader using the XML document located at the path specified in the xmlFilePath
parameter (line 27). Notice that the _valid variable is set to true to start, because it will be
assumed that the document is valid.
In line 28, the reader is passed to the constructor of the XmlValidatingReader named
vReader. From here, the method takes care of setting properties on vReader and then attaches
the ValidationEventHandler event handler to a callback function named Validation
CallBack. After these preparatory steps have taken place, vReader then reads through and
validates the XML document against the appropriate DTD or schema (line 42).
If any errors occur during validation, ValidationCallBack() will be called and the global
variable named _valid will be set to false. If the _logError variable is true, information
about the error will be written to a log file that can be reviewed later. After processing within
the ValidationCallBack() handler has finished, control will return to the Validate() method
and the value of the _valid variable will be returned to the calling ASP.NET page.
Calling the Validator class from within an ASP.NET page is as simple as instantiating the
class, creating the XmlSchemaCollection (optional), and then calling the Validate() method
while passing in the proper parameters.
Listing 5.7 Calling the Validator Class from an ASP.NET Page (validateSchema.aspx)
1: void Page_Load(object sender, System.EventArgs e) {
2: string xmlFilePath = Server.MapPath(“golfersNotValid(XSD).xml”);
3: string logFile = Server.MapPath(“validationErrors.log”);
4:
5: XmlSchemaCollection schemaCol = new XmlSchemaCollection();
6: schemaCol.Add(“http://www.golfExample.com”,
7: Server.MapPath(“golfers.xsd”));
8: Validator validator = new Validator();
9: bool status = validator.Validate(xmlFilePath,schemaCol,true,logFile);
10: if (status) {
11: Response.Write(“Validation of golfersNotValid(XSD).xml “ +
12: “ was SUCCESSFUL!”);
13: //Call method to process XML document here
14: } else {
15: Response.Write(“Validation of golfersNotValid(XSD).xml “ +
16: “ failed! Check the “ +
17: “log file for information on the failure.”);
5
XMLTEXTREADER
XMLTEXTWRITER
18: }
19: }
AND
XML for ASP.NET Developers
166
In cases where specific user IDs and passwords must be specified to access XML documents
located at different URLs, the NetworkCredential class can be used along with the
CredentialCache class, as shown next. Using these classes presents an excellent mechanism
for accessing secured XML documents.
XmlTextReader xmlReader =
new XmlTextReader(“http://www.golfExample.com/xml/golfers.xml”);
NetworkCredential creds = new NetworkCredential(“golfNut”,”1putt”,
“lottaWater”);
CredentialCache credsCache = new CredentialCache();
credsCache.Add(new Uri(“www.golfExample.com”), “Basic”, creds);
credsCache.Add(new Uri(“xml.golfExample.com”), “Basic”, creds);
xmlReader.XmlResolver.Credentials = credsCache;
while (xmlReader.Read()) {
//....process nodes
}
The XmlTextWriter class performs the task of writing to an XML document in a forward-only,
cursor-style manner. Although all the properties and methods associated with the class won’t
be listed here, they are extremely simple to use after you’ve seen an example. To introduce you
to how the XmlTextWriter class works, Listing 5.8 shows how to use several of its properties
and methods.
The XmlTextWriter constructor accepts several different arguments. This example passes in a
document name (xmltextwriter.xml) to which all output can be written. It also passes in the
desired encoding type for the document by using the Encoding enumeration. After the class is
instantiated, several methods are called that allow for customization over what is included in
the XML document.
To start, the Formatting property is set to Formatting.Indented so that the resulting XML
document has a hierarchical indentation (line 8). Next, the XML declaration is added by pass-
ing in a Boolean value of true to the WriteStartDocument() method (line 9). Several nodes
are then added to the document through calling the appropriate methods. The specific methods
used in Listing 5.8 are shown next along with their description:
Method Description
WriteXmlDecl(boolean) Determines whether an XML declaration
should be added with a standalone attribute.
If true, the following will be added:
<?xml version=”1.0”
standalone=”yes” ?>
false will yield:
<?xml version=”1.0”
standalone=”no ?>
Using the XmlTextReader and XmlTextWriter Classes in ASP.NET
169
CHAPTER 5
Method Description
If you do not want any XML declaration,
simply omit the call to the WriteXmlDecl()
method.
WriteComment(comment) Adds an XmlComment node to the document.
WriteStartElement(elemName) Creates an XmlElement start tag using the
supplied argument as the tag name. This is
used when an element can contain more than
just attributes.
WriteEndElement() Creates an ending element tag. This method
must appear after the corresponding
WriteStartElement() method.
WriteAttributeString(attName,value) Creates an attribute on the appropriate ele-
ment using the supplied name and value
arguments.
WriteElementString This method is used when an element con-
(elemName, nameSpace, value) tains only a text node. It accepts the element
name, namespace URI, and text node value.
If you haven’t seen an EDI document before, you’ll get your first look in Listing 5.9. This sim-
AND
plified document represents a purchase order. If you have EDI experience, you’ll recognize
that this is not necessarily complete, but it will suffice for our purposes.
XML for ASP.NET Developers
170
This document contains a header section, two detail sections, and a summary section. Marked
up in XML, the document could look like the one shown in Listing 5.10.
Converting the EDI document shown in Listing 5.9 to the XML document shown in Listing
5.10 is surprisingly simple when the XmlTextWriter class is used. Listing 5.11 shows the code
to accomplish this task.
Using the XmlTextReader and XmlTextWriter Classes in ASP.NET
171
CHAPTER 5
41: }
AND
XML for ASP.NET Developers
172
After the EdiToXml class is instantiated and passed the path to the EDI and XML documents,
the code in lines 25–31 reads through the EDI document line by line using the StreamReader
class (part of the System.IO namespace). Each line is split into different tokens and is placed
into a string array based on the * separator. The string array (named tokens) is then passed to
the GenerateXml() method, which writes out the data found within the string array to an XML
document using the XmlTextWriter class. When you use this class, the code is nicely orga-
nized, easy to follow, and very efficient. To see the EdiToXml class in action, run the file
named EdiToXmlClient.aspx.
Summary
You should now have a good feel for how XML can be read and written using forward-only,
noncached mechanisms. The XmlTextReader and XmlTextWriter classes (along with their sup-
porting classes) pack a lot of power that can provide your ASP.NET/XML applications with
efficiency and speed. There’s still a lot more to learn, however, so don’t relax just yet!
In the next chapter, you’ll learn more about the DOM and see how you can use it to navigate
XML documents and to insert, update, delete, and move XML nodes.
5
XMLTEXTREADER
XMLTEXTWRITER
AND
Programming the Document CHAPTER
6
Object Model (DOM) with
ASP.NET
IN THIS CHAPTER:
• Welcome to the DOM 176
• In-Memory Versus Forward-Only Parsing 178
• Working with MSXML3 via Interop 179
• DOM Classes in the System.Xml Namespace
and Assembly 182
• The XmlNode Class 183
• The XmlDocument Class 189
• The XmlNodeList Class 200
• The XmlNamedNodeMap Class 201
• Selecting Nodes Within the DOM Using
Xpath 204
• Putting It All Together 205
• The XmlNodeReader Class 209
• XMLHTTPRequest Object 211
• Sample Application—Client/Server-Side
Hierarchical XML Menus 219
• Chapter 2 Sample Application Revisited 225
XML Basics
176
<body bgcolor=”#FFFFFF”>
<div id=”mainContent”
onMouseOver=”changeColor(‘#ff0000’,’mainContent’)”
onMouseOut=”changeColor(‘#000000’,’mainContent’)”
>
Testing this out
</div>
</body>
</html>
If you have done much DHTML programming, you’re probably painfully aware that the DOM
used in Internet Explorer 4 or later is very different from the one used in Netscape Navigator 4
(the Netscape 6 DOM is more like Internet Explorer’s DOM). Unfortunately, the two browsers
Programming the Document Object Model (DOM) with ASP.NET
177
CHAPTER 6
are so different that a page that works perfectly fine in one is very unlikely to work in the 6
other. The W3C recognized the problems caused by inconsistencies between DOMs and issued
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
a DOM Level 1 recommendation back in 1998. More information about this can be found at
http://www.w3.org/TR/REC-DOM-Level-1/introduction.html. Since releasing the DOM
Level 1 recommendation, the W3C also released DOM Level 2 and is currently starting work
on DOM level 3. The W3C states
The DOM Level 2 is made of a set of core interfaces to create and manipulate the structure
and contents of a document and a set of optional modules. These modules contain specialized
interfaces dedicated to XML, HTML, an abstract view, generic stylesheets, Cascading Style
Sheets, Events, traversing the document structure, and a Range object.
Many of the concepts mentioned in the W3C statement are new additions to the DOM specifi-
cation. Some of these include a style sheet object model, namespace support, an event model,
and support for text ranges. More information about the DOM Level 2 specification can be
found at http://www.w3.org/TR/DOM-Level-2/.
“What does all this have to with XML?” you ask. Actually—everything. Although the DOM
created by the browser isn’t the same because of form collections, image collections, and so
on, many of the same concepts apply to an XML document’s DOM. When an XML document
is first loaded and parsed, an internal representation (similar to a tree structure) of the docu-
ment is placed in memory. This structure is based on the concept of nodes with the root node
being followed by other children nodes. Like the DOM the browser creates, the DOM created
by a DOM-compatible XML parser is programmatically accessible using classes that help you
access specific nodes within the DOM structure. After these classes and their associated prop-
erties and methods are understood, virtually any node within an XML document can be
accessed, updated, inserted, or deleted.
To gain a better understanding of how the DOM works, Figure 6.1 shows a visual representa-
tion of the following XML document’s DOM:
<?xml version=”1.0”?>
<golfer>
<name>Dan Wahlin</name>
<courses>
<course>Pinetop Lakes CC</course>
<course>Ocotillo</course>
</courses>
</golfer>
As shown in Figure 6.1, the DOM is composed of the various items found within the XML
document. Each of these items is a node in the DOM structure. This means that the text under
the two course nodes is actually represented in the DOM by a text node. The example shown
XML Basics
178
is very basic and doesn’t include many of the nodes that could exist in a given XML DOM
(attribute nodes, comment nodes, and so on), but it does give you a visual overview of how the
DOM is structured.
golfer
name courses
FIGURE 6.1
Representation of the DOM.
The next sections will explain how you can gain access to an XML document’s DOM, both on
the server side with ASP.NET and on the client side with JavaScript. Before covering those
topics, however, let’s first revisit the differences between in-memory and forward-only parsing.
In Chapter 5, “Using the XmlTextReader and XmlTextWriter Classes in ASP.NET,” you saw 6
how the DOM memory limitations can be overcome by using the XmlTextReader class (and
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
helper classes). Rather than loading the entire document into memory as with the DOM, the
reader processes one node at a time by creating a stream that XML tokens can be read from.
This forward-only stream means that when the document’s root element is reached, very little
data is buffered in memory at any given time. When a child node is reached, its ancestors have
been parsed, but they are not in memory and going back to them is not an option. This type of
functionality allows one item at a time to be checked and processed rather than loading every-
thing into memory. It provides a very low memory alternative that is capable of working with
very large XML documents.
When should you use the DOM instead of the forward-only parsing model exhibited by the
XmlTextReader class? Unfortunately, the answer to this question isn’t a simple one because
each application has unique requirements. In general, XML documents that simply need to be
read and not updated should leverage the speed and efficiency provided by the XmlTextReader
class. Doing this helps ensure that the application remains scalable. On the other hand, XML
documents that need modification either in the form of updates, inserts, or deletes will benefit
from the flexibility of having the document’s structure in memory and should therefore use
classes associated with the DOM. Although this is a less scalable solution, it is certainly
needed in many situations. You’ll be introduced to the classes that can be used to manipulate
the DOM in a moment. Let’s first take a quick look at how MSXML3 can be used in ASP.NET
applications.
If you take the Interop route, you can port your XML application to the .NET platform without
rewriting all your existing code. To do this, you must follow a few simple steps:
1. Convert MSXML3.dll to managed code using the Tlbimp.exe utility file.
2. Import this newly converted file’s namespace into your ASP.NET page.
3. Begin the conversion process from ASP to ASP.NET.
To get started, the first step in the preceding list must be completed. The Tlbimp.exe utility
converts the type definitions found in a COM type library to a .NET assembly that contains
metadata about the COM type definitions. In sum, the utility takes the type library information
found within MSXML3 and converts it into a form that can be used within an ASP.NET page.
To make the conversion, go to a command prompt and type the following:
tlbimp msxml3.dll /out:c:\inetpub\wwwroot\bin\msxml3Managed.dll
You may need to adjust the output file path as necessary. The output path should point to the
appropriate bin directory where you’ll be running the application that uses MSXML3 in your
ASP.NET page. If the Tlbimp.exe utility ran successfully, a new file will show up at the loca-
tion specified by the out parameter. To view the metadata contained within this file, locate the
IL Disassembler tool (Start, Programs, Microsoft .NET Framework SDK, Tools) or go to the
command prompt and type the following from within the appropriate bin directory:
ildasm msxml3Managed.dll
When you open the newly created metadata file, you should see a screen similar to the one
shown in Figure 6.2.
FIGURE 6.2
msxml3Managed.dll metadata.
Programming the Document Object Model (DOM) with ASP.NET
181
CHAPTER 6
The IL Disassembler parses the metadata created by the Tlbimp.exe utility and organizes it by 6
interface. You’ll notice that the DOMDocument30 interface is shown in Figure 6.2, along with a
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
lot of other interfaces. By expanding the DOMDocument30 interface, you will be able to see
all the properties and methods available for use. You’ll notice that they are the same ones you
have worked with in your regular ASP pages, except that now they can be used with VB.Net or
C# to create an ASP.NET page. Listing 6.1 shows a simple example that uses the new
mxsml3Managed.dll assembly (mxsml3Managed namespace) to parse an XML document.
As you can see, all the normal MSXML3 objects and their associated properties and methods
are being used in the preceding example. One difference, however, is that with VBScript (and
JScript) everything was a variant or object and late bound. In converting code from an older
ASP page, for the new code to compile successfully, you will need to add in the appropriate
type information for each object that is used.
XML Basics
182
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
XmlNodeReader The XmlNodeReader class can be used to turn a DOM structure into a
stream.
XMLHTTPRequest Although not part of the System.Xml namespace, the XMLHttpRequest
object (installed with Internet Explorer 5 or later) can be used for sending and
receiving XML content over HTTP from the client browser.
Through instantiating the classes shown in Table 6.1, the DOM can be accessed and manipu-
lated as desired. To better understand how some of these classes can be used, the following
section provides a step-by-step look at the process of creating the DOM structure:
1. An XML document is loaded using the XmlDocument class:
XmlDocument doc = new XmlDocument();
doc.Load(Server.MapPath(“golfers.xml”));
3. The child nodes of the root node are accessed and assigned to a XmlNodeList collection
class:
XmlNodeList nodeList = node.ChildNodes;
4. While working with a particular node, the node’s attributes are assigned to an
XmlNamedNodeMap collection class:
The next few sections will discuss how to use these classes in ASP.NET. First up, the XmlNode
class.
Attribute Entity
CDATA EntityReference
Comment None
Document Notation
DocumentFragment ProcessingInstruction
DocumentType SignificantWhitespace
Element Text
EndElement Whitespace
EndEntity XmlDeclaration
The XmlNode class has many properties and methods that can be used to work with different
type of nodes within an XML document. Tables 6.3 and 6.4 list these properties and methods,
respectively.
Property Description
Attributes Returns an XmlNamedNodeMap collection object containing the list of
attributes for a given node. This applies only to nodes of type
XmlNodeType.Element.
BaseURI Retrieves the base URI of the current node.
ChildNodes Retrieves all children of this node.
FirstChild Returns the first child node of the current node.
HasChildNodes Returns a Boolean indicating whether this node has any child nodes.
InnerText (*) Gets or sets the concatenated values of the node and all its children.
InnerXml (*) Gets or sets the XML markup that represents the children of this
node.
IsReadOnly (*) Returns a Boolean indicating whether the node is read-only.
Item Returns the first child element with the specified name or
NamespaceURI.
LastChild Returns the last child of the current node.
LocalName Returns the name of the current node without the namespace prefix.
Name Returns the name of the current node.
NamespaceURI Returns the namespace URI of the current node.
NextSibling Returns the sibling node directly following this node.
Programming the Document Object Model (DOM) with ASP.NET
185
CHAPTER 6
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
NodeType Returns the current node’s type.
OuterXml (*) Returns the XML markup representing the current node and all its
children. This differs from the InnerXml property because the current
node’s markup is included.
OwnerDocument Returns the XmlDocument that contains this node.
ParentNode Returns the parent of the current node. Attribute nodes as well as sev-
eral other node types cannot have parents and therefore will return
null.
Prefix Gets or sets the namespace prefix for the current node.
PreviousSibling Returns the sibling node that directly precedes the node.
Value Gets or sets the value of the node.
(*) Indicates an extension to the W3C DOM
Method Description
AppendChild(newNode) Appends a new child node to the current node
at the end of the list of children.
Clone() (*) Creates a duplicate of the current node.
CloneNode(deep) Similar to the Clone() method except it
accepts a Boolean to determine whether all
children of the current node should be cloned
as well.
CreateNavigator() Creates an XPathNavigator Object used to
navigate the current DOM structure.
GetNamespaceOfPrefix(prefix) (*) Returns the namespace URI in the closest
xmlns declaration containing the supplied
prefix.
GetPrefixOfNamespace(URI) (*) Returns the namespace prefix in the closest
xmlns declaration containing the supplied URI.
InsertAfter(newNode,refNode) Inserts a new node after the specified reference
node.
InsertBefore(newNode,refNode) Inserts a new node before the specified refer-
ence node.
XML Basics
186
Using the properties and methods of the XmlNode class is no different from working with
properties and methods of other classes in ASP.NET.
It’s important to note that the XmlNode class is never instantiated like many classes you’re used
to working with in ASP.NET. For example, you don’t do the following:
XmlNode node = new XmlNode();
This is because the XmlNode class is an abstract base class that can be implemented by other
classes (as you’ll see later). An object can, however, be typed as XmlNode. To make this
clearer, you need to think a little bit differently, especially if you haven’t worked much in an
Programming the Document Object Model (DOM) with ASP.NET
187
CHAPTER 6
object-oriented language. For example, the following code creates and loads an XmlDocument 6
and then gets the root node. This node is then assigned to an object named oNode that is of
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
type XmlNode:
<%@ Import Namespace=”System.Xml” %>
<script language=”c#” runat=”Server”>
void Page_Load(object sender, EventArgs e) {
XmlDocument oDocument = new XmlDocument();
oDocument.Load(Server.MapPath(“golfers.xml”));
XmlNode oNode = oDocument.DocumentElement;
Response.Write(oNode.Name);
}
</script>
After the XmlNode object is created, a call is made to the object’s Name property. The value
returned by the property is written out to the Web page using a call to the Response object’s
Write() method. The sample uses the XmlDocument class, which we haven’t covered yet.
Don’t worry about its functionality at this point, because the next section will explain it in
greater detail.
Some of the most common properties you’ll use with the XmlNode class include Name,
NodeType, Value, and InnerText. Other common properties that can be used to access
specific nodes include the FirstChild, NextSibling, and ChildNodes properties. Listing 6.2
shows an example of using some of the properties mentioned previously:
NOTE
It is much more efficient to use the FirstChild and NextSibling properties rather
than the ChildNodes property. The ChildNodes property exists because it is in the
W3C spec and was included here simply to demonstrate how it handles indexing.
Lines 6–12 of Listing 6.2 use properties of the XmlNode class to get to other child node objects
and then write out the node name and value. Line 6 first gets to the root node by using the
DocumentElement property of the XmlDocument object (discussed next). The code then pro-
ceeds to get the first child node of the root (golfer), the first child node of the golfer node
(name), and the second child node of the name node (lastName). The remaining code simply
writes out the lastName node’s Name, Value, and Innertext. An if statement was added sim-
ply to check whether the node being analyzed is of type element through comparing it to the
XmlNodeType enumeration (line 9).
TABLE 6.5 Other DOM Classes That Extend the XmlNode Class
Object Name NodeType Description
XmlAttribute Attribute Represents an attribute object.
XmlCDataSection CDATA Prevents blocks of text from
being parsed by an XML
parser.
XmlComment Comment Represents an XML comment.
XmlDocumentFragment DocumentFragment Can be used to create tree sec-
tions for insertion into a docu-
ment. Useful for creating
sibling nodes to be added to a
parent.
XmlDocumentType DocumentType Contains information associated
with the document type declara-
tion.
Programming the Document Object Model (DOM) with ASP.NET
189
CHAPTER 6
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
XmlElement Element Represents an XML document
element object.
XmlEntity Entity Represents an entity found
within an XML document.
XmlEntity Reference EntityReference Represents an entity reference
node.
XmlNotation Notation Can contain a notation declared
in a DTD.
XmlProcessingInstruction ProcessingInstruction Represents a processing
instruction.
XmlText Text Represents the text content of an
element or attribute.
Because these classes inherit from the base XmlNode class, they all expose a base set of
properties and methods. For a full listing of these, consult the .NET SDK documentation.
Property Description
Attributes Returns an XmlAttributeCollection object containing the
list of attributes for a given node. This applies only to nodes
that are of type XmlNodeType.Element.
XML Basics
190
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
OuterXml (*) Returns the XML markup representing the current node and
all its children. This differs from the InnerXml property
because the current node’s markup is included.
OwnerDocument Returns the XmlDocument that contains this node.
ParentNode Returns the parent of the current node. Attribute nodes as
well as several other node types cannot have parents and
therefore will return null.
Prefix Gets or sets the namespace prefix for the current node.
PreserveWhiteSpace (*) Determines whether whitespace will be preserved in the
XML document.
PreviousSibling Returns the sibling node that directly precedes the current
node.
Value Gets or sets the value of the current node.
(*) Indicates an extension to the W3C DOM
Method Description
AppendChild(newNode) Appends a new child node to the current node.
Clone() (*) Creates a duplicate of the current node.
CloneNode(deep) Similar to the Clone() method except it accepts
a Boolean to determine whether all children of
the current node should be cloned as well.
CreateAttribute(name) Creates an XmlAttribute node with the argu-
ment name. Other override methods exist.
CreateCDataSection(text) Creates a XmlCDataSection node that contains
the argument text.
CreateComment(text) Creates a XmlComment node that contains the
argument text.
CreateDocumentFragment() Creates an empty XmlDocumentFragment
object. This can then be used to create indepen-
dent sections of a document. No namespace can
be used with this method.
XML Basics
192
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
GetPrefixOfNamespace(URI) (*) Returns the namespace prefix in the closest
xmlns declaration containing the supplied URI.
ImportNode(node,deep) Imports a node (and possibly all descendants
depending on the Boolean value of deep) from
another document into the current document.
InsertAfter(newNode,refNode) Inserts a new node after the specified reference
node.
InsertBefore(newNode,refNode) Inserts a new node before the specified refer-
ence node.
Load(filePath or URL) (*) Loads an XML document from the location
specified in the argument. This can be a file
path or URL. Alternatively, other override
methods allow a document to be loaded from a
TextReader object, a stream, or an XmlReader
object.
LoadXML(string) (*) Loads an XML document using the argument
string. For example, you can create an XML
document on the fly by using:
doc.LoadXml(“<root><child>test</child>
</root>”)
Now that you’ve seen all that the XmlDocument class has to offer, let’s see how to use a few of
its more important properties and methods. If you’re using Visual Studio.NET, you’ll be happy
to know that the built-in IntelliSense feature will provide you with access to all the properties
and methods associated with a particular XML object.
If you’re not using Visual Studio, don’t be too concerned. There will be plenty of examples in
the next few sections that will show you just how easy the XmlDocument class and its related
classes are to work with.
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
}
catch (Exception exc) {
Response.Write(exc.ToString());
}
oDocument.Save(Response.Output);
}
</script>
This code will load the golfers.xml XML document and write it out to the Response object
for display in the browser. Had the golfers.xml file path not been found, an error message
stating, System.IO.FileNotFoundException: Couldn’t find file. would appear in the
browser because a try....catch block was implemented.
The Save() method can also save to a file assuming write access is provided to the IUSR
account in IIS (see the IIS administration manual for more information on the IUSR account).
However, this method does not allow for saving to a URL. Although this is a limitation, it
makes sense especially because the remote server may not be under your control. If you do
have control over the remote server, getting around this limitation can be accomplished by
posting the XML document to an ASP.NET (or other) page located on the remote server or by
using classes found within the System.Net namespace to push the XML using FTP, HTTP
PUT, and so on. This page can then parse the document and perform the save operation.
Saving updates made to an XML document can be accomplished by calling the
XmlDocument’s Save() method:
<%@ Import Namespace=”System.Xml” %>
<script language=”C#” runat=”Server”>
void Page_Load(object sender, EventArgs e) {
String sXML = “<?xml version=\”1.0\”?><root><test>”;
sXML += “<testChild>Testing!</testChild></test></root>”;
String savePath = Server.MapPath(“testNodes.xml”);
XmlDocument oDocument = new XmlDocument();
oDocument.LoadXml(sXML);
Response.Write(“Document saved to: “ + savePath + “<p>”);
Response.Write(“Click <a href=\”testNodes.xml\”>here</a> to view the
➥file”);
oDocument.Save(savePath);
}
</script>
Although the preceding example saves to a file, the Save() method has several override meth-
ods that allow a document to be saved to a stream object, such as an XmlTextWriter class
(discussed in Chapter 5 and again later in this chapter).
XML Basics
196
There may be times when you have an XML document in the form of a string and would like
to parse and load it into the DOM. The XmlDocument class provides this capability through its
LoadXML() method (shown in the previous example). The following code segment contains a
string named SXML that contains a short XML document. This string can be parsed and then
loaded into the DOM as shown next:
<%@ Import Namespace=”System.NewXml” %>
<script language=”C#” runat=”Server”>
void Page_Load(object sender, EventArgs e) {
String sXML = “<?xml version=\”1.0\”?><root><test>”;
SXML += “<testChild>Testing!</testChild></test></root>”;
XmlDocument oDocument = new XmlDocument();
oDocument.LoadXml(sXML);
Response.ContentType = “text/xml”;
oDocument.Save(Response.Output);
}
</script>
NOTE
This input string must be a valid Unicode string. If you have encoded XML bytes in
memory, use the Load() method that takes a Stream object.
• Entity references 6
• DTD declarations
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
• XML declarations
• Whitespace
• Significant whitespace
Creating different items using the XmlDocument class involves instantiating the class, loading
the XML document (or XML string), calling the appropriate create method, and then calling
one of the append/prepend methods to add the new elements to an existing node in the docu-
ment. Listing 6.3 loads an XML string into the DOM and then creates two empty element
nodes named testB and testC. These nodes are then appended to the root node of the
document.
After creating the two new nodes using the CreateElement() method, the XML document root
is found by calling the XmlDocument’s DocumentElement property. Next, the AppendChild()
method is called (lines 12 and 13), which adds the newly created nodes directly under the root
node. The resulting XML document now includes the newly added testB and testC elements:
XML Basics
198
<?xml version=”1.0”?>
<root>
<testA>
<testChild>Testing!</testChild>
</testA>
<testB/>
<testC/>
</root>
Before moving on to the next topic, let’s consider an alternative method for writing out
XML nodes to a document. In Chapter 5 you were provided with an in-depth look at the
XmlTextReader class. This class is a forward-only stream that can be used to read large XML
documents quickly without using much memory. You’ll recall that the XmlTextReader class is
complemented by an XmlTextWriter class. This class performs the task of writing to an XML
document in a forward-only, cursor-style manner.
The next example shown in Listing 6.4 demonstrates how the XmlTextWriter class can be used
to create an XML fragment that is then inserted into a master XML document. The alternative
would be to create the nodes using the XmlDocument or a related class and then place each
node within the master document. The pros and cons of this approach follow.
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
24: writer.WriteAttributeString(“city”,”Phoenix”);
25: writer.WriteAttributeString(“state”,”Arizona”);
26: writer.WriteAttributeString(“name”,”Ocotillo”);
27: writer.WriteEndElement();
28: writer.WriteStartElement(“course”,null);
29: writer.WriteAttributeString(“city”,”Tempe”);
30: writer.WriteAttributeString(“state”,”Arizona”);
31: writer.WriteAttributeString(“name”,”Ken McDonald”);
32: writer.WriteEndElement();
33: writer.WriteStartElement(“course”,null);
34: writer.WriteAttributeString(“city”,”Phoenix”);
35: writer.WriteAttributeString(“state”,”Arizona”);
36: writer.WriteAttributeString(“name”,”Ahwatukee CC”);
37: writer.WriteEndElement();
38: writer.WriteEndElement(); //favoriteCourses
39: writer.WriteEndElement(); //golfer
40: writer.Flush();
41:
42: Response.ContentType = “text/xml”;
43: XmlDocument doc = new XmlDocument();
44: doc.LoadXml(sb.ToString());
45: XmlDocumentFragment frag = doc.CreateDocumentFragment();
46: foreach (XmlNode node in doc.ChildNodes) {
47: frag.AppendChild(node);
48: }
49: doc.Load(masterDoc);
50: doc.DocumentElement.PrependChild(frag);
51: doc.Save(Response.Output);
52: writer.Close();
53: sw.Close();
54: }
55: catch (Exception e) {
56: Response.Write(e.ToString());
57: }
58: finally {
59: if (writer != null) {
60: writer.Close();
61: }
62: if (sw != null) {
63: sw.Close();
64: }
65: }
66: }
67: </script>
XML Basics
200
By using the XmlTextWriter to create the XML fragment, you eliminate having to rely on the
XmlDocument to create objects one at a time and then insert them into the DOM tree. Although
this can simplify the process of adding nodes, some performance implications are present that
you may not realize. First, the XML fragment that is created must still be loaded into a DOM
structure. Second, the master XML document must also be loaded into the DOM so that the
fragment can be inserted into it. Creating these DOM structures takes time and resources.
Adding nodes by using the XmlDocument class alone can minimize these performance costs
because the DOM structure is loaded and parsed only once. The moral of the story is that if
you need to randomly add one or more nodes into an existing XML document and optimal per-
formance and memory management is an absolute necessity, use the XmlDocument class to cre-
ate the new nodes. If you’re creating an XML document from scratch and simply saving it to a
location, use the XmlTextWriter class.
Property Description
Count Returns the number of nodes in the XmlNodeList collection starting with a base of 1.
Method Description
GetEnumerator() Allows the collection of nodes to be enumerated using a simple
foreach iteration.
Item(index) Returns the node in the collection at a position equal to the index passed in.
The first node will have an index equal to 0.
oDocument.DocumentElement.GetElementsByTagName(“golfer”);
Response.Write(“<b>Length of NodeList:</b> “ + oNodeList.Count +
6
THE DOCUMENT
OBJECT MODEL
“<p>”);
PROGRAMMING
Response.Write(“<b>For/Next Loop</b><br>”);
for (int i=0;i<oNodeList.Count;i++) {
Response.Write(oNodeList[i].Name + “<br>”);
}
Response.Write(“<p><b>For/Each Loop</b><br>”);
foreach (XmlNode node in oNodeList) {
Response.Write(node.Name + “<br>”);
}
}
</script>
The previous code shows two ways of walking through an XmlNodeList collection. The first
uses a for..next loop and the second uses a foreach loop. This sample also shows how to
use the Count property as well as the Item() method. You’ll notice, however, that the Item
method is abbreviated by using oNodeList[i] rather than oNodeList.Item[i].
NOTE
Using the foreach mechanism is more efficient then doing a for..next loop. This is
because oNodeList.Count has to check to see what the actual count is every time you
use it in the loop. To make the for..next version more efficient than shown, it would
be better to store the value returned by oNodeList.Count in a local variable and ref-
erence this during the looping process.
NOTE
The XmlNamedNodeMap class can also be used to hold other node collections, such as
notations or entities. If you’re interested in working with a collection of attributes,
the XmlNamedNodeMap class can be used; however, the XmlAttributeCollection class
extends this class to provide additional functionality specific to attributes.
XML Basics
202
Let’s take a look at the properties and methods of this object in tables 6.10 and 6.11.
Property Description
Count Returns the number of nodes in the XmlNamedNodeMap collection.
Method Description
GetEnumerator() Allows the collection of nodes to be enumerated using a
simple foreach iteration.
Item(index) Returns the node in the collection at a position equal to the
index passed in. The first node will have an index equal to 0.
GetNamedItem(name) Returns the appropriate XmlNode from the XmlNamedNodeMap
collection.
RemoveNamedItem(name) Removes the appropriate XmlNode (an attribute) from the
XmlNamedNodeMap collection.
SetNamedItem(node) Adds an XmlNode to the collection using the node’s Name property.
The process of using XmlNamedNodeMap properties and methods is demonstrated in Listing 6.5.
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
14: Response.Write(att.Name + “ = “ + att.Value + “<br>”);
15: }
16: Response.Write(“<p><b>XmlAttributeCollection For/Next “ +
17: “Loop</b><br>”);
18: XmlAttributeCollection oAttCol = oNode.Attributes;
19: for (int i=0;i<oAttCol.Count;i++) {
20: Response.Write(oAttCol[i].Name + “ = “ + oAttCol[i].Value +
21: “<br>”);
22: }
23:
24: Response.Write(“<p><b>XmlAttributeCollection <i>SetNamedItem()” +
25: “</i>”);
26: Response.Write(“ using XmlDocument: test</b><br>”);
27: XmlAttribute oNewAtt = oDocument.CreateAttribute(“test”);
28: oNewAtt.Value = “Hi!”;
29: oNode.Attributes.SetNamedItem(oNewAtt);
30: XmlAttributeCollection oAttCol2 = oNode.Attributes;
31: for (int i=0;i<oAttCol2.Count;i++) {
32: Response.Write(oAttCol2[i].Name + “ = “ + oAttCol2[i].Value +
33: “<br>”);
34: }
35: Response.Write(“<p><b>XmlAttributeCollection <i>” +
36: “RemoveNamedItem()</i>:”);
37: Response.Write(“ skill</b><br>”);
38: oAttCol2.RemoveNamedItem(“skill”);
39: for(int i=0;i<oAttCol2.Count;i++) {
40: Response.Write(oAttCol2[i].Name + “ = “ + oAttCol2[i].Value +
41: “<br>”);
42: }
43: Response.Write(“<p><b>XmlAttributeCollection <i>GetNamedItem()” +
44: “</i>: skill</b><br>”);
45: try {
46: Response.Write(oAttCol2.GetNamedItem(“skill”).Value);
47: }
48: catch (Exception e) {
49: Response.Write(“<font color=\”#ff0000\”>” +
50: e.GetBaseException() + “</font>”);
51: Response.Write(“<br>The above error occurred because” +
52: “ the skill attribute no”);
53: Response.Write(“ longer exists.”);
54: }
55: }
56: </script>
XML Basics
204
The previous code shows different ways of walking through an XmlNamedNodeMap collection.
The first uses a foreach loop (lines 13–15) and the second uses a for loop (lines 19–21). Line
30 introduces a class that was briefly mentioned at the beginning of this section. This class is
referred to as the XmlAttributeCollection and it can do more than simply allow attributes
to be enumerated through. For example, it has several useful methods in addition to those
found on the XmlNamedNodeMap class that can be used for inserting or removing attributes
from a collection. These additional methods include Append(), CopyTo(), InsertAfter(),
InsertBefore(), Prepend(), Remove(), RemoveAll(), and RemoveAt(). Listing 6.5 shows how
the SetNamedItem(), RemoveNamedItem(), and GetNamedItem() methods (discussed in Table
6.11) can be used. Both methods accept the node that should be set or removed in the
collection.
This example will search through the requests.xml file and return all element nodes (no mat- 6
ter how deeply nested they are) that have a name equal to contactName and a text node value of
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
“Dave.” If the statement does not match any nodes, no error will be raised. Rather, the looping
process will not occur because the Count property of the XmlNodeList will be 0.
The SelectSingleNode() method works in a similar manner. However, instead of returning an
XmlNodeList collection, it will return only a single node. In cases where an XPath statement
matches more than one node, the first node will be returned. An example of using the
SelectSingleNode() method is shown next:
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
54: }
55:
56: private void WalkTheTree(XmlNode oNodeToWalk) {
57: indent++;
58: XmlNodeList oNodeList = oNodeToWalk.ChildNodes;
59: for (int j=0;j<oNodeList.Count;j++) {
60: XmlNode oCurrentNode = oNodeList[j];
61: if (oCurrentNode.HasChildNodes) {
62: WriteNodeName(oCurrentNode,indent);
63: WalkTheTree(oCurrentNode);
64: indent—;
65: } else {
66: WriteNodeName(oCurrentNode,indent);
67: }
68: }
69: }
70:
71: private void WriteNodeName(XmlNode node,int iIndent) {
72: int h = 0;
73: for (int k=0;k<(iIndent * 10);k++) {
74: output.Append(“ ”);
75: }
76: if (node.NodeType == XmlNodeType.Text) { // Text node
77: output.Append(“<font color=’#ff0000’>” + node.Value +
78: “</font><br>”);
79: } else {
80: if (node.Attributes.Count > 0) {
81: XmlNamedNodeMap oNamedNodeMap = node.Attributes;
82: output.Append(“<b>” + node.Name + “</b> (“);
83: foreach (XmlAttribute att in oNamedNodeMap) {
84: if (h!=0) output.Append(“ ”);
85: h++;
86: output.Append(“<i>” + att.Name +
87: “</i>=\”” +
88: att.Value + “\””);
89: }
90: output.Append(“)<br>\n\n”);
91: } else {
92: output.Append(“<b>” + node.Name +
93: “</b><br>\n\n”);
94: }
95: } // end if
XML Basics
208
The following list provides a step-by-step look at what is being accomplished in the previous
code:
1. The XmlDocumentTest class is instantiated (line 103).
2. The XmlDocumentTest class’s ParseDoc() method is called and passed the path of the
XML document to be parsed along with the paths of the file to validate against and the
log file to write errors to (lines 104 and 105).
3. The ParseDoc() method creates an XmlSchemaCollection object and adds in the
name of the file to validate against (lines 16 and 17). The Validate() method of the
Validator class (shown in Chapter 5) is then called, which returns a Boolean value and
assigns it to a variable named status (line 19). If status is true, processing of the
XML document continues. Otherwise, a message is returned to the end user stating that
the file was not valid (lines 39–41).
4. An XmlTextReader object is instantiated and passed into the Load() method of the
XmlDocument class (Lines 22–24).
5. The XML document’s root node is found and assigned to the XmlNode object named
oNode (line 25).
7. The WriteNodeName() method sets how far the node should be indented and checks to 6
see what type of node it was passed. Text nodes are written out in red and element nodes
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
are bold. If a node is an element type and has attributes, the attributes are enumerated
using a foreach command. Each attribute and its associated value are italicized (lines
71–96).
8. On leaving the WriteNodeName() method, an XmlNodeList is created containing all chil-
dren of the root node. Each of these children is looped through one by one. In doing so,
the WriteNodeName() method is called to write out each node’s information. If the node
being analyzed contains its own child nodes, the WalkTheTree() method is called and
the node containing the child nodes is passed as an argument (Lines 56–69).
9. The WalkTheTree() method performs the same steps as shown in step 8. It creates an
XmlNodeList and checks for child nodes located under each node in the list. If child
nodes exists, it recursively calls itself and the process starts over. This occurs until all
child nodes have been analyzed.
10. After all the nodes and their associated child nodes have been analyzed and added to
the output variable, output is returned and written out to the ASP.NET page (lines 104
and 105).
XMLHTTPRequest Object 6
THE DOCUMENT
OBJECT MODEL
The XMLHTTPRequest object is not actually part of the .NET platform. In fact, the object is
PROGRAMMING
intended to be used on the client side for retrieving data from remote HTTP servers. Although
Internet Explorer 5 or later is the only browser that supports the object at the current time (it
can also be used from within client/server applications), it presents a nice way to call pages
(such as ASP.NET pages) that live on remote servers, especially in an intranet environment.
The data returned from the ASP.NET page can be bound to controls or elements within the
HTML page. The great part about the object is that the call to the remote server can be made
without having to reload the client’s HTML page each time. This provides the end user with a
richer experience.
Using the XMLHTTPRequest object on the client to call remote XML files or ASP.NET pages
can be quite efficient, depending on your application’s needs. The example you will see shortly
shows how to use the object to view different IT request form submissions added to an XML
document. You first saw a very primitive form of this application in Chapter 2, “XML for
ASP.NET Basics,” and the revised version of this application will be covered at the end of this
chapter.
After a bid request has been added to an XML document (named requests.xml), you can
use the XMLHTTPRequest object to view an individual user’s entry. Rather than using the
XMLHTTPRequest object, you could have the user submit a request to an ASP.NET page that
retrieves the data and returns HTML to the user. Doing that would, of course, require the
client’s page to be reloaded each time a new request is submitted to the server. Using the
XMLHTTPRequest object, the user can submit a request to the server and view the desired
request without having to reload the page each time. Before going into the example, take a
moment to familiarize yourself with the XMLHTTPRequest object’s properties and methods in
tables 6.12 and 6.13.
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
abort() Cancels the current HTTP request.
Example:
oXMLHttp.abort();
getAllResponseHeaders() Retrieves the values of all the HTTP headers delimited by
a carriage-return line feed.
Example:
sHeader = oXMLHttp.getAllResponseHeaders();
getResponseHeader Returns the value of an HTTP header from the response
(header_name) body, depending on what header is passed into the
function.
Example:
sHeader = oXMLHttp.getResponseHeader(“Content-
Type”);
As with all the XML objects you’ve worked with to this point, the XMLHTTPRequest object is
very simple to work with after you have a handle on the properties and methods it exposes.
The example that follows shows how some of the properties and methods can be used.
XML Basics
214
As mentioned earlier, the example will create an HTML page that allows a user to view
previously submitted requests to an XML file. The user will be able to select an ID from a
drop-down box and then view the response without having to leave the HTML page.
To start the process, the ASP.NET page (Listing6.8.aspx) fills a drop-down box with infor-
mation about each request in the request.xml file. This is accomplished by using the XmlNode
class’s SelectNodes() method along with several other classes covered earlier in the chapter.
After the user selects a request from the drop-down box, the drop-down’s onChange event fires
and a client-side function named getRequest() is called.
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
35: requestNode.ChildNodes;
36: foreach (XmlNode requestChildNode
37: in requestChildrenNodeList) {
38: if (intCount < 1) {
39: table += “<tr><td>” +
40: requestChildNode.Name.ToUpper() +
41: “:</td>”;
42: table += “<td><div id=’” +
43: requestChildNode.Name +
44: “‘></div></td></tr>”;
45: }
46:
47: switch (requestChildNode.Name.ToUpper()) {
48: case “JOBCODE”:
49: jobCode = requestChildNode.InnerText;
50: break;
51: case “CONTACTNAME”:
52: contactName = requestChildNode.InnerText;
53: break;
54: case “DATETIME”:
55: dateTime = requestChildNode.InnerText;
56: break;
57: }
58: }
59:
60: options += “<option value=\”” + attID + “\”>”;
61: options += jobCode + “ - Entered By: “ +
62: contactName + “ (“ + dateTime + “)”;
63: options += “</option>”;
64: }
65: intCount++;
66: } //foreach
67: } //if
68: optionsDiv.InnerHtml = options;
69: tableRowsDiv.InnerHtml = table;
70: }
71: </script>
72: <html>
73: <head>
74: <meta http-equiv=”Content-Type” content=”text/html;
75: charset=windows-1252”>
76: <SCRIPT LANGUAGE=”javascript”>
77: <!—
XML Basics
216
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
123: </tr>
124: <tr>
125: <td align=”left” valign=”middle” width=”34%”>
126: <font face=”Arial, Helvetica, sans-serif” size=”2”>
127: <b>Select A Request:</b>
128: </font>
129: </td>
130: <td align=”left” valign=”top” colspan=”2”>
131: <select name=”requestID” onChange=”getRequest()”>
132: <option>Select One:</option>
133: <div id=”optionsDiv” runat=”server” />
134: </select>
135: </td>
136: <td valign=”top” align=”left”
137: colspan=”3” width=”27%”>
138: </td>
139: </tr>
140: </table>
141: <table ID=”dataTable” cellpadding=”5”>
142: <div style=”font-family:arial”
143: id=”tableRowsDiv” runat=”Server” />
144: </table>
145: </form>
146: </body>
147: </html>
Figure 6.3 shows a screenshot of the page generated by the code shown in Listing 6.8.
After a user selects a specific IT request from the drop-down box, the getRequest() method
locates the request id the user selected and assigns it to the id variable. Next, a QueryString
is dynamically created that includes the request id the user desires to see. Notice that the URL
points to an ASP.NET page (lines 81–83).
The XMLHTTPRequest object is instantiated and a call is placed to its open() method (line 86).
After this method has been successfully called, the information is sent to the remote server
using the send() method shown in line 87. After the remote server has responded, the resulting
XML document text is placed within an XML data island (named requestData) that is embed-
ded in the Web page. An XML data island is an Internet Explorer 5 or later feature that allows
XML documents to be embedded in a Web page. Using the updateBindings() method (lines
92–106), the data island is used to populate div tags located on the page programmatically. An
alternative way of accomplishing the same task would be to bind the fields to the data island
using the dataSrc and dataFld element attributes.
XML Basics
218
FIGURE 6.3
The IT request form.
On the server side, an ASP.NET page named Listing6.8.aspx checks to see whether a
QueryString variable named status contains a value of getXML. If it does, it takes the id
value that was passed on the QueryString, instantiates an XmlDocument, and calls the
SelectSingleNode() method passing in an XPath statement that includes the request id. The
resulting XML fragment is then returned to the client-side XMLHTTPRequest object.
The code shown in Listing 6.9 shows this process and further illustrates how to use the
XmlNode’s SelectSingleNode() method to select a specific node within the requests.xml
file.
LISTING 6.9 Selecting Nodes with the SelectSingleNode() Method. (This page is called
from listing6.8.aspx.)
1: <%@ Import Namespace=”System.Xml” %>
2: <script language=”C#” runat=”Server”>
3: void Page_Load(Object Src, EventArgs E) {
4: Response.ContentType = “text/xml”;
5: Response.Expires = -1;
6:
7: string ID = Request.QueryString[“id”];
8: string status = Request.QueryString[“status”];
9:
10: switch (status.ToUpper()) {
11: case “GETXML”:
12: GetRequestXML(ID);
Programming the Document Object Model (DOM) with ASP.NET
219
CHAPTER 6
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
14: default:
15: break;
16: }
17: }
18:
19: private void GetRequestXML(string requestID) {
20: XmlDocument doc = new XmlDocument();
21: try {
22: doc.Load(Server.MapPath(“requests.xml”));
23: }
24: catch (Exception c) {
25: Response.Write(“<root>There are currently no” +
26: “ requests to view.<root>”);
27: }
28: //Perform XPath query using SelectSingleNode() method
29: string xpath = “//request[@id=’” + requestID + “‘]”;
30: XmlNode requestNode = doc.SelectSingleNode(xpath);
31: if (requestNode != null) {
32: Response.Write(requestNode.OuterXml);
33: } else {
34: Response.Write(“<root><request>NO ID</request></root>”);
35: }
36:
37: }
38: </script>
The end result of this process is that the user can select different request IDs and view their
associated data without having to leave the Web page. This solution is server friendly because
only a few calls are made to the server to obtain data; that allows the end user to be more effi-
cient and happy, which hopefully results in fewer calls to the help desk.
Sample Application—Client/Server-Side
Hierarchical XML Menus
Now that you’ve seen what you can do with the main XML classes, let’s leverage that knowl-
edge to construct a menu similar to the Start menu in Windows. XML was chosen for this
application simply because of its capability to support hierarchical structures and because of
the speed with which it can be accessed from a Web server. The application will use an XML
document named menuItems.xml and an ASP.NET file named menus.aspx on the server side.
On the client side, a cascading style-sheet document and a JavaScript document will be used
XML Basics
220
for the Dynamic HTML (DHTML). All these files working together produce the menu applica-
tion shown in Figure 6.4.
FIGURE 6.4
Screenshot of the XML-based menu application.
The foundation of the application is the XML document because it contains the data used for
the menus. A portion of the document is shown next:
<menuItem>
<hyperLink/>
<name>About Us</name>
<menuItem>
<hyperLink>/calendar/default.aspx</hyperLink>
<name>Calendars</name>
</menuItem>
<menuItem>
<hyperLink>/govt/default.aspx</hyperLink>
<name>Government</name>
</menuItem>
<menuItem>
<hyperLink>/pdf/orgchart.pdf</hyperLink>
<name>Organization Charts</name>
</menuItem>
</menuItem>
As you can see, the XML document relies on one of XML’s core strengths: the capability to
describe hierarchical relationships. Looking at the preceding document, you can see that
Programming the Document Object Model (DOM) with ASP.NET
221
CHAPTER 6
several <menuItem> tags are nested within each other, creating a parent/child relationship. This 6
relationship is used to create menus that can contain submenus as seen in the Windows Start
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
menu.
On the server side, a custom class named XmlMenu has a recursive function used to handle
navigating through the XML document. It uses a few of the classes you’ve seen before, such
as the XmlDocument and XmlNode classes, and a few you may not know about yet, called
ArrayList and StreamWriter. These different classes are used to access node values for dis-
play or to store node information. As the menuItem nodes in the XML document are navigated,
the different menu structures are placed into ArrayLists. After all the arrays are created, a call
to the Response object sends the formatted menus up to the client where the DHTML
JavaScript code takes over.
Now that you have a feel for the application, let’s take a look at the code that starts things off
on the server side (menus.aspx):
<%@ Import Namespace=”XmlHierMenu” %>
<%
// Create paths to xml files
string filepath = Server.MapPath(“menuItems.xml”);
string filepath2 = Server.MapPath(“menuItems2.xml”);
string filepath3 = Server.MapPath(“menuItems3.xml”);
As shown, the code starts by instantiating the XmlMenu class, which is part of the XmlHierMenu
namespace in the XmlMenu class. After the class is instantiated, a call is made to its
CreateMenu() method. This method accepts the name of the menu and the path to the menu’s
XML document. In this example, three menus named menu1, menu2, and menu3 will be
created for use in an ASP.NET page.
The CreateMenu() method is shown in Listing 6.10:
XML Basics
222
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
46: startArray.Add(strVariable);
47: }
48: }
49: i++;
50: }
51:
52: startArray.TrimToSize();
53: arrayNamesArray.Add(startMenu);
54: for (i=0;i<startArray.Count;i++) {
55: strTemp += startArray[i];
56: }
57: arrayHolderArray.Add(strTemp);
58:
59: //Reverse Array order so we don’t have to worry about the ZIndex
60: arrayHolderArray.Reverse();
61: arrayNamesArray.Reverse();
62:
63: //Loop through arrays and write out divs and their individual content
64: for (i=0;i<_arrayNamesArray.Count;i++) {
65: strOutput.Append(“<div id=’” + _arrayNamesArray[i].ToString() +
“‘ class=’clsMenu’>”);
66: strOutput.Append(_arrayHolderArray[i].ToString());
67: strOutput.Append(“</div>\n”);
68: }
69: arrayHolderArray.Clear();
70: arrayNamesArray.Clear();
71:
72: if (_blnStaticMenus) {
73: StreamWriter writer = new StreamWriter(File.Open(_strSaveToFile,
74: FileMode.OpenOrCreate, FileAccess.Write));
75: writer.Write(strOutput.ToString());
76: writer.Flush();
77: if (writer !=null) writer.Close();
78: }
79: return strOutput.ToString();
80: } //CreateMenus
This method begins by loading the XML document (line 10). It then finds the root node of the
document so that its child nodes can be looped through recursively. As the children are looped
through, a call is made to the HasChildNodes property of the XmlNode class and a count is
taken on how many child nodes the current node has (lines 25–47). If it only has one child
node, then it’s a text node; if it has more than one, the children must be processed recursively.
XML Basics
224
As each node is evaluated, its information is put into a string that will be used on the client
side to manipulate its color and position. This string is then added to an ArrayList named
startArray (lines 34 and 46).
To walk through the child nodes of a menuItem node, a call is made to the WalkTree() method,
which accepts the node to walk through and evaluate. The code for this method is shown in
Listing 6.11:
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
37: }
38:
39: tempArray.TrimToSize();
40: arrayNamesArray.Add(_strCurrentMenu);
41: for (int i=0;i<tempArray.Count;i++) {
42: strTemp += tempArray[i];
43: }
44: arrayHolderArray.Add(strTemp);
45: strCurrentMenu = _strCurrentMenu.Substring(0,_
46: strCurrentMenu.Length-2);
47: //Exiting function so go back to previous menu version
48: intLevel -= 1;
49: tempArray.Clear();
50: } // WalkTree
When the walkTree() method is called, the node currently being analyzed is passed in and
more checks are run to see if it also has child nodes that need to be walked through (lines 9
and 10). If it does, WalkTree() is called again and the node to walk through is passed into the
method. This process of calling the same function to go deeper into a tree structure is called
recursion. It’s a very powerful programming technique that requires little code. You saw an
example of recursion a little earlier in the “Putting It All Together” section.
During this entire process, the menuItem nodes that are found are parsed and their appropriate
hyperLink and name node values are placed into ArrayLists. When the entire XML docu-
ment has been parsed, the ASP.NET page writes the contents of the array lists out using the
Response object. From this point on, client-side code controls the DHTML menus
(XMLMenuScript.js).
This application provides a good look at how recursion can be used to get at XML nodes and
traverse hierarchical parent/child relationships. Although the client-side code that goes with
this application is designed to work only in Internet Explorer 4 or later, you can download a
slightly different version of the application that is cross-browser from
http://www.TomorrowsLearning.com.
and have seen some of their properties and methods, this simple application can be converted
to a true XML application using the System.Xml classes rather than the file system objects
used in the more primitive example. Using the XmlDocument and XmlNode classes, you’ll be
able to capture form data and then add nodes to an XML document dynamically. The following
is a fragment of the XML document that will be created when a user submits a request:
<request id=”462001|32633|PM”>
<dateTime>4/6/2001 3:26:33 PM</dateTime>
<startDate>05/06/2001</startDate>
<endDate>05/13/2001</endDate>
<dueDate>04/15/2001</dueDate>
<projectTime>40</projectTime>
<projectLength>hours</projectLength>
<cost>10000</cost>
<jobCode>ITV1000</jobCode>
<subject>IT Contractor Request</subject>
<jobTitle>Manager/Director</jobTitle>
<contactName>Dan Wahlin</contactName>
<contactDepartment>Tomorrow’s Learning</contactDepartment>
<contactStreet>1234 Anywhere St.</contactStreet>
<contactCity>Phoenix</contactCity>
<contactState>AZ</contactState>
<contactZip>85244</contactZip>
<contactPhone>123-1234</contactPhone>
<contactFax>123-4321</contactFax>
<contactEmail>[email protected]</contactEmail>
<jobStreet>1234 Anywhere St.</jobStreet>
<jobCity>Phoenix</jobCity>
<jobState>AZ</jobState>
<jobZip>85244</jobZip>
</request>
The form used to gather data from the end user is similar to the one used in Chapter 2. Upon
submitting the form, however, the code used to capture the data has been given a major over-
haul. Listing 6.12 shows the part of the ASP.NET page called when data is submitted by an
end user:
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
8: bool blnStatus = true;
9:
10: XmlDocument doc = new XmlDocument();
11: try {
12: doc.Load(sXMLFilePath);
13: }
14: catch (Exception e) {
15: //File doesn’t exist yet so make one
16: doc.LoadXml(“<?xml version=\”1.0\”?><” + sRootName +
17: “></” + sRootName + “>”);
18: }
19:
20: //******************************************************************
21: //*********************** Prevent duplicate ids *****************
22: //******************************************************************
23: string sXPath = “//request[contains(@id,’” + newID + “‘)]”;
24: XmlNodeList requestNodeList = doc.SelectNodes(sXPath);
25: int lastNodeIndex = requestNodeList.Count;
26: if (lastNodeIndex == 0) {
27: sID = newID;
28: } else {
29: // Get the last node in the NodeList that has this id.
30: XmlElement lastRequestElement =
31: (XmlElement)requestNodeList.Item(lastNodeIndex-1);
32: sID = lastRequestElement.GetAttribute(“id”);
33:
34: //Ensure user didn’t enter request twice
35: XmlElement contactElement =
36: (XmlElement)doc.SelectSingleNode(“//request[@id=’” +
37: sID + “‘]/contactName”);
38: if (contactElement != null) {
39: //Ensure element isn’t empty
40: if (!contactElement.IsEmpty) {
41: if (contactElement.InnerText.ToUpper().Trim() ==
42: Request.Form[“contactName”].ToUpper().Trim()) {
43: Response.Write(“It appears you are attempting” +
44: “ to enter two “ +
45: “entries for the same request.” +
46: “ Processing cannot continue.”);
47: blnStatus = false;
48: }
49: }
50: }
XML Basics
228
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
96: doc.Save(sXMLFilePath);
97: output += “Your information was successfully stored!<p>”;
98: output += “<a href=\”requests.xml\”>View XML File Contents</a>”;
99: content2.InnerHtml = output;
100: content2.Visible = true;
101: } else {
102: Response.Write(sStatus);
103: }
104: }
The code starts off in Line 10 by instantiating the XmlDocument class. Line 12 attempts to load
an existing XML document. If an error occurs because of the document not being found, a call
is made to the XmlDocument’s LoadXml() method and a simple XML document shell is created
(lines 16 and 17).
After we have an XML document (either an existing one or one created from scratch), the next
few lines of code help ensure that a unique id attribute is being added to the XML document.
Because the ID is based on a combination of date and time, it is certainly possible that more
than one user could submit a form request at the same time (down to the second) as another
user. If a request element in the XML document already has an id attribute equal to the one
the current user is submitting, the code will dynamically append an identity number on the end
of the id and then use that id to represent the new user’s request submission.
To see if another id already exists and matches up with the id the user is submitting, a call
to the XmlDocument class’s SelectNodes() method is made (line 24). An XPath statement is
passed into this method that uses the XPath contains function (covered in Chapter 3, “XPath,
XPointer, and XLink,” to check whether the id exists in any other id attributes already in
the XML document. If the call to the SelectNodes() method does not return one or more
nodes, the id does not already exist and can be used to uniquely identify the user’s request
submission.
If the id is found within another id already in the document, Lines 25–66 make the user’s sub-
mitted id unique to the XML document through performing a few alterations to it. First, the
last node that contains the same id that the user is submitting is found by accessing the index
of the last child node returned from the earlier call to the SelectNodes() method (lines 30 and
31). This id is then assigned to the sID variable. sID is used to create another XPath statement
that is passed into the XmlDocument class’s SelectSingleNode() method. The node that
matches will be a contactName node because of the Xpath statement (lines 36 and 37). This
XPath query is done to ensure that the id in question isn’t being compared to an id that was
XML Basics
230
just placed into the XML document by the same user because of accidentally submitting the
form twice (hey, users can do some weird things!). The value of the contactName node cur-
rently selected is then compared to the value found in Request.Form[“contactName”] (lines
41–48). If the two values are not equal, we know that the id being submitted is being com-
pared to an id in the XML document that was submitted by a different user at the exact same
time.
At this point, we can go ahead and make the necessary alternations to the id submitted by the
user to ensure that it is unique. We first use the latest id that contains the current user’s ID in it
(already placed in the sID variable earlier) and check the end of it for a “-” character. If one
exists, multiple entries have been submitted at the same time. From here, the code grabs the
numbers after the “-” character, converts them to an integer, and then adds one to the number.
The resulting number is then appended onto the current user’s submitted id to make it unique.
To help make this more clear, here’s how three id entries all submitted at the same time by
different users would look in the XML document:
1st Submission id: 462001|32633|PM
2nd Submission id: 462001|32633|PM-1
3rd Submission id: 462001|32633|PM-2
Having read through all that, you should have a new appreciation for databases and the way
they use identity fields to keep rows unique! Before we go further into the code, it’s important
to realize that this method of generating IDs is simply for example, so that you can see some
of the different XML classes in action. If you truly want a unique ID to be generated, you
would be better off instantiating a class that is designed to return a GUID or leverage features
found in ADO.NET (covered in Chapter 8, “Leveraging ADO.NET’s XML Features Using
ASP.NET”).
After the ID issue has been resolved, sID is added as the value for the id attribute (line 78) and
the attribute is added to the request node created earlier. Line 81 uses the XmlNode class’s
appendChild method to add the request node to the root node of the XML document. Now
that a container exists for all the user data in the form of the request node, the code loops
through the Request object’s form collection to gather the user data.
Lines 83–93 contain the looping logic. You’ll notice that as each item in the collection is
looped through, a call is made to a method named AddNode(). This is a static method that
exists in a class named XmlFunctions. The AddNode() method’s sole purpose is to add the data
entered by the user into the request node. The code used to accomplish this is shown next:
public static string AddNode(string sNodeName,string sNodeText,
XmlNode oNodeToAppendTo,XmlDocument oDoc) {
if (sNodeText == “”) sNodeText = “null”;
try {
Programming the Document Object Model (DOM) with ASP.NET
231
CHAPTER 6
THE DOCUMENT
OBJECT MODEL
PROGRAMMING
oNodeToAppendTo.AppendChild(oNewNode);
}
catch (Exception exc) {
return exc.ToString();
}
return “”;
}
The AddNode() method accepts four different arguments. These are shown next:
Argument Description
sNodeName Name of the new node to add to the container node.
sNodeText Value that the new node being created will contain.
Example:
<sNodeName>sNodeText</sNodeName>
oNodeToAppendTo Node object that the new node should be appended to.
oDoc XmlDocument object that will be used to create new element nodes
and their associated text nodes.
As described previously, the function accepts the new node’s name (sNodeName), the data the
node will describe (sNodeText), the XmlNode object to which the newly created node will be
appended to (oNodeToAppendTo), and the XmlDocument object that will be used to create new
nodes (oDoc). After these arguments are passed, the function simply creates a new node,
appends a text node to it, and then appends the newly created node to the request node in the
XML document being worked with. This process occurs for each element in the form that the
user submits.
Every user’s data will be added to the XML document in the same fashion as shown in Listing
6.12. Comparing this version of the application to the one used in Chapter 2, you can see how
the XML document stays well formed throughout the entire process. This is in contrast to hav-
ing to manually add the ending root tag immediately before viewing the XML document, as
was done in Chapter 2’s example. Was this solution a little more difficult to develop? Probably
so, assuming you’re already familiar with the classes used in Chapter 2’s application. However,
it provides much more power and flexibility in manipulating the XML document. If users want
the capability to view past submissions, a query mechanism similar to the one shown earlier in
the “XMLHTTPRequest Object” section could be built to allow for this. Try doing that using only
the objects used in Chapter 2’s example! It could be done, but parsing string data manually can
tend to get boring and tedious.
XML Basics
232
Summary
In this chapter you’ve been presented with a comprehensive look at many of the DOM classes
available in the System.Xml namespace. These classes include the XmlNode, XmlDocument,
XmlNodeList, XmlNamedNodeMap, and XmlNodeReader. You also saw how to use the
XMLHTTPRequest object on the client side. By using these classes and their various properties
and methods, XML documents loaded into the DOM can be accessed and manipulated
programmatically.
In the next chapter you’ll learn about Extensible Stylesheet Language Transformations (XSLT)
and see how XML documents can be transformed into other structures.
Transforming XML with XSLT CHAPTER
7
and ASP.NET
IN THIS CHAPTER
What Is XSLT? 234
What Is XSLT?
During the development of the XML specification, the W3C working group realized that for
XML to reach its full potential, a method of transforming XML documents into different for-
mats needed to exist. At some time or another, an application that has the capability to work
with XML documents will need to display or structure the data in a different format than speci-
fied in the document. If the only method for accomplishing this task necessitates programmati-
cally transforming the XML document into the appropriate format by using an XML parser
paired with a programming language, the power of having a cross-platform and language-
independent XML language would be lost. Some method of transforming XML documents
into different formats such as HTML, flat files, Wireless Markup Language (WML), and even
other forms of XML needed to be devised so that it could be used on any platform and with
any language.
To accommodate this transformation process, Extensible Stylesheet Language Transformations
(XSLT) was created. Version 1.0 of the XSLT specification reached recommended status at the
W3C in November of 1999 (http://www.w3.org/TR/1999/REC-xslt-19991116) and many
XML parsers now provide full XSLT support. The .NET framework provides 100% compli-
ance with the XSLT version 1.0 specification.
What exactly is XSLT useful for and why would you, as an ASP.NET developer, want to learn
about it? The answer boils down to the capability of XSLT to transform XML documents into
different formats that can be consumed by a variety of devices, including browsers, Personal
Digital Assistants (PDAs), Web-enabled phones, and other devices that will appear in the near
future.
Transformations can also be useful in situations where an XML document’s structure does not
match up well with an application that will accept the data within the document. An XML doc-
ument may contain the appropriate data to be imported into a database, for example, but may
not be structured in a way that the application performing the import expects. For example, the
application may be better prepared to handle element-based XML documents rather than ones
with a lot of attributes, as shown in the following document:
<?xml version=”1.0”?>
<root>
<row id=”1” fname=”Dan” lname=”Wahlin”/>
<row id=”2” fname=”Heedy” lname=”Wahlin”/>
<row id=”3” fname=”Danny” lname=”Wahlin”/>
<row id=”4” fname=”Jeffery” lname=”Wahlin”/>
</root>
Using XSLT, this document can be transformed into a structure that the application is better
suited to work with:
Transforming XML with XSLT and ASP.NET
235
CHAPTER 7
<?xml version=”1.0”?>
<root>
<row>
<id>1</id>
<fname>Dan</fname>
<lname>Wahlin</lname>
</row>
<row>
<id>2</id>
<fname>Heedy</fname>
<lname>Wahlin</lname>
</row>
7
TRANSFORMING
AND ASP.NET
<id>3</id>
<fname>Danny</fname>
<lname>Wahlin</lname>
</row>
<row>
<id>4</id>
<fname>Jeffery</fname>
<lname>Wahlin</lname>
</row>
</root>
This chapter teaches you how to perform this type of transformation—as well as many
others—by covering the following topics:
• The transformation process
• The XSLT language
• .NET classes involved in transforming XML
XML
XML
HTML
PDF
WML
ETC.
XSLT Processor
XSLT
FIGURE 7.1
The XSLT transformation process.
XSLT Templates
Before looking more closely at how to build XSLT documents, it’s important that you under-
stand what the building blocks of these documents are. Although XSLT stands for Extensible
Stylesheet Language Transformations, an alternative name for it could potentially be
Extensible Template Language Transformations. Why? The answer is because of its reliance on
templates to process and create a particular output structure. The W3C provides the following
statement about templates:
A stylesheet contains a set of template rules. A template rule has two parts: a pattern which is
matched against nodes in the source tree and a template which can be instantiated to form part
of the result tree. This allows a stylesheet to be applicable to a wide class of documents that
have similar source tree structures.
If you have ever used templates in Excel, Word, or PowerPoint, you know that they provide a
basic structure that can be reused for specific purposes. For example, every time you submit an
expense report, you may be accustomed to filling out a template in Word that is designed for
this purpose. The template likely has specific form fields built in so that every expense report
being submitted looks the same. There may be other templates that are used for purchase
orders. The point is that the templates are geared to match up with a specific task, such as cre-
ating an expense report, a purchase order, or some other activity.
Templates in XSLT function in much the same way, except that they are geared to match up
with nodes in an XML document. XSLT templates provide a way to process and structure data
contained within elements and attributes in the source XML document. Their basic purpose is
to provide a template structure that can be processed when a particular node in the source
XML document is discovered.
So how do templates work? The XSLT processor described earlier is provided with two tree
structures to walk through. The first is the structure for the source XML document and the
Transforming XML with XSLT and ASP.NET
237
CHAPTER 7
second is the XSLT document itself. After these two structures are provided, the XSLT proces-
sor attempts to match element or attribute names found in the XML document with templates
contained in the XSLT tree structure. This matching process uses XPath expressions that are
embedded within the XSLT document. When a node found within the XML document matches
a template in the XSLT document, that template is processed.
Processing of templates found within an XSLT document normally starts with a template that
matches the root node of the XML document and proceeds down to its children. When a tem-
plate is processed, the output is added to the third tree structure mentioned earlier that is used
in building the output document. 7
TRANSFORMING
AND ASP.NET
efficient in cases where an XML document contains repetitive items. Each time an element,
attribute, text node, and so on is found, it is matched up with the appropriate template via
XPath expressions. If a given node does not have a matching template, no processing will
occur on it, and the next section of the XML document is processed. In cases where a match-
ing node is found, the template takes care of generating the proper output structure based on
data/nodes contained within the node.
So that you can see templates in action, the next section introduces you to a simple XSLT doc-
ument. The sections that follow describe in greater detail how to use templates and other parts
of the XSLT language.
Listing 7.2 presents an XSLT document that can be used to transform the XML just shown into
HTML. The different elements used in this document are discussed later.
TRANSFORMING
AND ASP.NET
33: <xsl:apply-templates select=”name”/>
34: <tr class=”blackText”>
35: <td width=”12%” align=”left”><b>Skill: </b></td>
36: <td width=”12%” align=”left”>
37: <xsl:value-of select=”@skill”/>
38: </td>
39: <td width=”12%” align=”left”><b>Handicap: </b></td>
40: <td width=”12%” align=”left”>
41: <xsl:value-of select=”@handicap”/>
42: </td>
43: <td width=”12%” align=”left”><b>Clubs: </b></td>
44: <td width=”40%” align=”left”>
45: <xsl:value-of select=”@clubs”/>
46: </td>
47: </tr>
48: <tr>
49: <td colspan=”6”> </td>
50: </tr>
51: <tr class=”blackText”>
52: <td colspan=”6” class=”largeBlackText”>
53: Favorite Courses
54: </td>
55: </tr>
56: <tr>
57: <td colspan=”2”><b>City: </b></td>
58: <td colspan=”2”><b>State: </b></td>
59: <td colspan=”2”><b>Course: </b></td>
60: </tr>
61: <xsl:apply-templates select=”favoriteCourses”/>
62: </table>
63: <p/>
64: </xsl:template>
65: <xsl:template match=”name”>
XML for ASP.NET Developers
240
To transform the XML document shown in Listing 7.1 using the XSLT document shown in
Listing 7.2, the code shown in Listing 7.3 can be used:
AND ASP.NET
HTML that can be rendered in a browser. The result of this transformation is shown in
Figure 7.2.
FIGURE 7.2
Transforming an XML document into HTML.
The code shown in Listings 7.2 and 7.3 may look quite foreign to you at this point. Don’t let
that worry you, though, because each portion of the code will be broken down to show how the
different pieces work together. In addition to covering the XSLT language, the examples that
follow also demonstrate XPath expressions as a point of review. For a detailed explanation of
XPath, refer to Chapter 3, “XPath, XPointer, and XLink.” Before examining the .NET classes
involved in transforming XML to other structures, let’s first examine what pieces are involved
in constructing an XSLT document.
XML for ASP.NET Developers
242
• <xsl:transform>
Although you can use either element as the root of an XSLT document, the samples that follow
throughout this chapter use the xsl:stylesheet element. You can certainly substitute the
xsl:transform element instead if you feel more comfortable using it.
Two different items must also be included for an XSLT document to follow the guidelines
found in the XSLT specification. These are a local namespace declaration as well as an
attribute named version. The inclusion of the xsl:stylesheet element, the namespace decla-
ration, and the version attribute are shown next:
<?xml version=”1.0”?>
<xsl:stylesheet xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”
version=”1.0”
>
</xsl:stylesheet>
uses. Failure to list these parts correctly will result in an error being returned by the XSLT
processor.
NOTE
XSLT version 1.1 was in Working Draft at the time this section was written. XSLT style
sheets that use new features found in this version will need to let the XSLT processor
know by changing the version attribute to 1.1.
7
XSLT Elements
Although not every element listed in Table 7.1 is discussed in this section, you will be exposed
to the more common elements and see how they can be used to transform XML into formats
such as HTML, WML, and even EDI.
NOTE
The HTML generated by an XSLT document must conform to the rules outlined in the
XML specification. All elements must be closed, including <img>, <input>, and all the
other elements that normally do not need to be closed in HTML. Attributes used on
elements must be quoted, a beginning and ending element tag’s case must match,
and so on. Keep in mind that the XSLT processor knows how to work only with well-
formed XML and knows nothing about the tags used in HTML, WML, and so on. As a
result, everything within the XSLT document must follow the XML rules.
TRANSFORMING
AND ASP.NET
14: .borders {border-left:1px solid #000000;
15: border-right:1px solid #000000;
16: border-top:1px solid #000000;
17: border-bottom:1px solid #000000;}
18: </style>
19: </head>
20: <body bgcolor=”#ffffff”>
21: <span class=”largeBlackText”>
22: <b>List of</b>
23: <xsl:if test=”count(//golfer) > 0”>
24:  
25: <xsl:value-of select=”count(//golfer)”/> 
26: </xsl:if>
27: <b>Golfers</b>
28: </span>
29: <p/>
30: <xsl:apply-templates/>
31: </body>
32: </html>
33: </xsl:template>
34: <xsl:template match=”golfers”>
35: <xsl:apply-templates select=”golfer”/>
36: </xsl:template>
37: <xsl:template match=”golfer”>
38: <table class=”borders” border=”0” width=”640”
39: cellpadding=”4” cellspacing=”0” bgcolor=”#efefef”>
40: <xsl:apply-templates select=”name”/>
41: <tr class=”blackText”>
42: <td width=”12%” align=”left”>
43: <b>Skill: </b>
44: </td>
45: <td width=”12%” align=”left”>
46: <xsl:attribute name=”style”>
XML for ASP.NET Developers
248
TRANSFORMING
AND ASP.NET
103: <xsl:value-of select=”firstName”/> 
104: <xsl:value-of select=”lastName”/>
105: </td>
106: </tr>
107: </xsl:template>
108: <xsl:template match=”favoriteCourses”>
109: <xsl:apply-templates/>
110: </xsl:template>
111: <xsl:template match=”course”>
112: <xsl:call-template name=”writeComment”/>
113: <tr class=”blackText”>
114: <td colspan=”2” align=”left”>
115: <xsl:value-of select=”@city”/>
116: </td>
117: <td colspan=”2” align=”left”>
118: <xsl:value-of select=”@state”/>
119: </td>
120: <td colspan=”2” align=”left”>
121: <xsl:value-of select=”@name”/>
122: </td>
123: </tr>
124: </xsl:template>
125: <xsl:template name=”writeComment”>
126: <xsl:comment>List Course Information</xsl:comment>
127: </xsl:template>
128: </xsl:stylesheet>
The following explanation walks through each element used in Listing 7.4.
Line 1:
1: <?xml version=”1.0”?>
Lines 2–3:
2: <xsl:stylesheet xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”
3: version=”1.0”>
This line contains the first XSLT element used in the document: xsl:stylesheet. As shown
earlier, this element has an associated namespace declaration and version attribute. The
xsl:transform element could also be used here. One of these two elements will always be the
root node of the XSLT document.
Line 4:
4: <xsl:output method=”html” indent=”yes”/>
The xsl:output element is used to specify what type of format will be created in the result
tree. The method attribute can contain values of xml, html, or text. This element is a top-level
element, meaning that it must be a child of the xsl:stylesheet element to be used properly.
The different attributes that can be used to describe this element are the following: method,
version, encoding, omit-xml-declaration, standalone, doctype-public, doctype-system,
cdata-section-elements, indent, media-type. This element includes the indent attribute,
which will indent the result tree to show its hierarchical structure. XSLT processors do not
have to honor the indentation request. If the processor does support indentation, the manner in
which the indentation is implemented is up to the processor.
Lines 5–33:
5: <xsl:template match=”/”>
6: <html>
7: <head>
8: <style type=”text/css”>
9: .blackText {font-family:arial;color:#000000;}
10: .largeYellowText {font-family:arial;
11: font-size:18pt;color:#ffff00;}
12: .largeBlackText {font-family:arial;
13: font-size:14pt;color:#000000;}
14: .borders {border-left:1px solid #000000;
15: border-right:1px solid #000000;
16: border-top:1px solid #000000;
17: border-bottom:1px solid #000000;}
18: </style>
19: </head>
20: <body bgcolor=”#ffffff”>
21: <span class=”largeBlackText”>
22: <b>List of</b>
23: <xsl:if test=”count(//golfer) > 0”>
24:  
25: <xsl:value-of select=”count(//golfer)”/> 
Transforming XML with XSLT and ASP.NET
251
CHAPTER 7
26: </xsl:if>
27: <b>Golfers</b>
28: </span>
29: <p/>
30: <xsl:apply-templates/>
31: </body>
32: </html>
33: </xsl:template>
Here’s an example of the first template definition specified in the XSLT document. Templates
contain structured information that will be processed and output to the result tree. They are
7
processed when the XPath pattern found in the match attribute matches a node found in the
NOTE
It’s worth repeating that that the <html> and <body> elements contained within the
template are simply standard XML elements to the XSLT processor. It knows nothing
about HTML elements and simply cares that the elements follow the XML rules. Only
when processing has completed and the result tree is rendered in an application that
understands HTML tags, will the <html> and <body> elements have any presentation
purpose.
The xsl:template element can have the attributes shown in Table 7.2.
Lines 23–26:
23: <xsl:if test=”count(//golfer) > 0”>
24:  
25: <xsl:value-of select=”count(//golfer)”/> 
26: </xsl:if>
This portion of the XSLT document shows how the xsl:if element can be used to test a par-
ticular condition. In this case, an XSLT function named count() is used. This function takes
an XPath statement as input and returns the number of nodes as output. You’ll learn more
about XSLT functions in the next section.
The xsl:if element must have an attribute named test, which converts a valid XPath state-
ment to a Boolean. In this case, the test checks to see whether the number of golfer nodes
exceeds the value of 0. If the test returns true, the content (referred to as the template body)
between the xsl:if start and end tags is processed.
Line 25 contains the xsl:value-of element, which is used frequently in XSLT documents
to write out the value of a particular node to the result tree. This element must contain an
attribute named select. The value of the attribute must contain a valid XPath expression. The
node (or nodes) returned by the expression is converted to a string. In this case, the number of
golfer nodes within the XML document is returned and placed in the result tree structure.
Transforming XML with XSLT and ASP.NET
253
CHAPTER 7
The xsl:apply-templates element provides a way to call templates that may match with
7
other items contained in the source XML document. Before explaining what this element does
The golfers node in the source XML document matches up with the template defined in these
lines of the XSLT document. As the XSLT processor is attempting to match templates with
nodes in the XML document, any node with a name of golfers will automatically be
processed by this template. Within the template, you’ll notice that the content is very sparse,
except for the inclusion of an xsl:apply-templates element. With the context node now
being the golfers node, calling xsl:apply-templates will send the processor looking for
templates that match up with child nodes of the golfers node.
You’ll notice that line 30 includes an attribute named select that applies to the xsl:apply-
templates element. This attribute accepts a valid XPath expression as a value. In this case, it
selects a template that matches up with a node named golfer. Because the golfers node con-
tains golfer nodes only as children, including this attribute is unnecessary and is shown only
to exemplify its use. If, however, the golfers node contained child nodes other than the
golfer node and we wanted the golfer node template to be processed first, the inclusion of
the select attribute would be more appropriate. This will become more clear as the next tem-
plate is discussed.
XML for ASP.NET Developers
254
Lines 37–99:
37: <xsl:template match=”golfer”>
38: <table class=”borders” border=”0” width=”640”
39: cellpadding=”4” cellspacing=”0” bgcolor=”#efefef”>
40: <xsl:apply-templates select=”name”/>
41: <tr class=”blackText”>
42: <td width=”12%” align=”left”>
43: <b>Skill: </b>
44: </td>
45: <td width=”12%” align=”left”>
46: <xsl:attribute name=”style”>
47: <xsl:choose>
48: <xsl:when test=”@skill=’excellent’”>
49: color:#ff0000;font-weight:bold;
50: </xsl:when>
51: <xsl:when test=”@skill=’moderate’”>
52: color:#005300;
53: </xsl:when>
54: <xsl:when test=”@skill=’poor’”>
55: color:#000000;
56: </xsl:when>
57: <xsl:otherwise>
58: color:#000000;
59: </xsl:otherwise>
60: </xsl:choose>
61: </xsl:attribute>
62: <xsl:value-of select=”@skill”/>
63: </td>
64: <td width=”12%” align=”left”>
65: <b>Handicap: </b>
66: </td>
67: <td width=”12%” align=”left”>
68: <xsl:value-of select=”@handicap”/>
69: </td>
70: <td width=”12%” align=”left”>
71: <b>Clubs: </b>
72: </td>
73: <td width=”40%” align=”left”>
74: <xsl:value-of select=”@clubs”/>
75: </td>
76: </tr>
77: <tr>
78: <td colspan=”6”> </td>
79: </tr>
80: <tr class=”blackText”>
Transforming XML with XSLT and ASP.NET
255
CHAPTER 7
TRANSFORMING
AND ASP.NET
94: </td>
95: </tr>
96: <xsl:apply-templates select=”favoriteCourses”/>
97: </table>
98: <p/>
99: </xsl:template>
This template does the bulk of the work in Listing 7.4 by matching up with all golfer nodes in
the source XML document. When the template is processed, the shell structure for an HTML
table is written out. This table will be used to present all the information about a specific golfer.
Line 40 uses the xsl:apply-templates and provides a pattern for the template that should be
called by using the select attribute. By providing a pattern equal to name, only a template that
matches up with the pattern will be processed. Why didn’t we simply call xsl:apply-templates
and not worry about which of the context node’s child node templates were called? The answer
is that we want to ensure that the template with a pattern matching the name child node is
processed before any other children of the context node (golfer, in this case).
After the template matching the name node is called, processing will be done on that template
and then return to the golfers template. Specifically, the XSLT processor will jump back to
the next statement in the golfer template that immediately follows the call to <xsl:apply-
templates select=”name”/>.
Lines 46–74 exhibit several of the XSLT elements shown earlier in Table 7.1. To start things
off, line 46 contains an xsl:attribute element named style. This XSLT element adds a style
attribute to the <td> tag in line 45. The value of the attribute is dynamically assigned based on
a series of conditional tests. To accomplish the tests, the xsl:choose, xsl:when, and xsl:
otherwise elements are used. These elements function in a manner similar to the switch,
case, and default keywords used when coding a switch statement in C#.
The conditional test starts with the xsl:choose element. It can be followed by as many
xsl:when elements, as needed. The xsl:when element must contain a mandatory attribute
XML for ASP.NET Developers
256
named test that contains the expression to test. If the test returns true, the content between
the xsl:when starting and ending tags will be assigned to the value of the style attribute. The
test performed in line 48 checks to see whether an attribute of the context node (remember that
the context node is golfer at this point) named skill has a value equal to excellent. If it
does, the style attribute will have a value of color:#ff0000;font-weight:bold;. Assuming
the skill attribute does have a value of excellent, the actual structure added on completion of
the xsl:choose block testing will be the following:
<td width=”12%” align=”left” style=”color:#ff0000;font-weight:bold;”>
If the first xsl:when returns false, testing will continue down the line. If no xsl:when tests
return true, the xsl:otherwise block will be hit and the style attribute will be assigned a
value of color:#000000; (lines 57–59).
After the style attribute has been added to the <td> tag, processing continues with line 62,
which adds the value of the skill attribute to the table column by using the xsl:value-of ele-
ment discussed earlier. Lines 64–94 continue to add additional columns to the table and write
out the value of attributes found on the context node (the golfer node).
When processing in the golfers template completes, the xsl:apply-templates element is
again used along with a select attribute that points to a template pattern of favoriteCourses
(line 96). This template will be discussed later.
Lines 100–107:
100: <xsl:template match=”name”>
101: <tr>
102: <td colspan=”6” class=”largeYellowText” bgcolor=”#02027a”>
103: <xsl:value-of select=”firstName”/> 
104: <xsl:value-of select=”lastName”/>
105: </td>
106: </tr>
107: </xsl:template>
The template declaration shown in line 100 matches up with all name nodes found within the
XML document. This template is called from within the golfer template discussed earlier (see
line 40). Processing of the template is limited to writing out a new row in the table (line 101)
followed by a column containing the values of the firstName and lastName elements. These
values are written to the result tree by using the xsl:value-of element(lines 103 and 104).
Lines 108–127:
108: <xsl:template match=”favoriteCourses”>
109: <xsl:apply-templates/>
110: </xsl:template>
111: <xsl:template match=”course”>
Transforming XML with XSLT and ASP.NET
257
CHAPTER 7
TRANSFORMING
AND ASP.NET
125: <xsl:template name=”writeComment”>
126: <xsl:comment>List Course Information</xsl:comment>
127: </xsl:template>
The template matching favoriteCourses contains no functionality other than to call xsl:
apply-templates. This is because it contains no attributes and acts only as a parent container
element. Because the favoriteCourses node contains only child nodes named course, calling
xsl:apply-templates will result in only one template match.
Processing within the template that matches the course nodes is limited to adding a new row
(line 113) with columns containing the values for attributes named city, state, and name.
Each course node found within the source XML document will be matched with this template
and the appropriate attribute values will be written out.
Line 112 introduces a new XSLT element that hasn’t been covered to this point. The
xsl:call-template element can be used to call a template in a similar manner as calling a
function within C# or VB.NET. Calling a template in XSLT is accomplished by identifying the
name of the template to call via a name attribute. The template that is called must, in turn, have
a matching name attribute as shown in line 125.
Calling templates can be useful when a template doesn’t match up with a given node in an
XML document but needs to be accessible to process commonly used features or perform cal-
culations. For example, if your XSLT code needs to walk through a list of pipe-delimited
strings, a template can be called recursively until each piece of data within the string has been
processed. You’ll see a concrete example of using the xsl:call-template element in conjunc-
tion with the xsl:with-param and xsl:param elements toward the end of this chapter.
Line 128:
128: </xsl:stylesheet>
XML for ASP.NET Developers
258
The XSLT language follows all the rules outlined in the XML specification. As such, the
xsl:stylesheet element must be closed.
This example has shown how you can use some of the main elements found in the XSLT spec-
ification. For more information on some of the other elements not covered in the previous
example, refer to the XSLT version 1.0 specification (http://www.w3.org/TR/1999/REC-
xslt-19991116) or pick up a copy of a book titled XSLT Programmer’s Reference
(ISBN 1-861003-12-9).
TRANSFORMING
AND ASP.NET
32: <street>1233 Books Way</street>
33: <city>AnyTown</city>
34: <zip>85784</zip>
35: </address>
36: </row>
37: </root>
LISTING 7.6 The Result of Transforming the XML Document in Listing 7.5
1: <?xml version=”1.0”?>
2: <root>
3: <row id=”1” fname=”Dan” lname=”Wahlin”>
4: <address type=”home”>
5: <street>1234 Anywhere St.</street>
6: <city>AnyTown</city>
7: <zip>85789</zip>
8: </address>
9: <address type=”business”>
10: <street>1234 LottaWork Ave.</street>
11: <city>AnyTown</city>
12: <zip>85786</zip>
14: </address>
15: </row>
16: <row id=”2” fname=”Elaine” lname=”Wahlin”>
17: <address type=”home”>
18: <street>1234 Anywhere St.</street>
19: <city>AnyTown</city>
20: <zip>85789</zip>
21: </address>
22: <address type=”business”>
23: <street>1233 Books Way</street>
24: <city>AnyTown</city>
XML for ASP.NET Developers
260
Breaking the XSLT document down into individual pieces reveals a few new things not seen in
previous examples. First, Line 4 uses the xsl:output element to specify an output format of
xml. It also specifies that the XML declaration should be included. This is done by setting the
omit-xml-declaration attribute to no. Because this is the attribute’s default value, it could
have been left out altogether.
Lines 6–10 take care of setting the starting template (the one that matches the document node)
needed in the XSLT document. This template simply adds a node named root to the result tree
Transforming XML with XSLT and ASP.NET
261
CHAPTER 7
and then triggers the process of looking for other templates that match up with nodes in the
source XML document.
The template matching the row element node writes a row element to the result tree. The bulk
of the transformation process occurs in lines 13–24. To start, three different attributes are
added to the row element by using the xsl:attribute element. The value of these attributes is
obtained by using the xsl:value-of element to access the appropriate elements in the source
XML document.
After the attributes are added, the xsl:for-each element is used to loop through all address
elements. This element simply takes the name of the node-set to loop through as the value of 7
TRANSFORMING
AND ASP.NET
from the source to the result tree, the xsl:copy-of element is used to simply copy over the
address element (and all its children) to the result tree. Had we wanted only to copy the
address element itself and not the children, we could have used the xsl:copy element instead.
However, utilizing the xsl:copy-of element prevents us from having to create each element
(address, street, city, zip) dynamically by using the xsl:element or xsl:copy elements.
Now that you’ve had an opportunity to see some of the most common XSLT elements in
action, let’s take a look at a few more that can help make your XSLT documents more dynamic
and flexible.
Variables in XSLT
XSLT variables are used to avoid calculating the same result multiple times. Although very
similar to variables used in C# or any other programming language, XSLT variables can be set
once but cannot be updated after they are set. The value assigned to a variable is retained until
the variable goes out of scope. What, you say! XSLT variables can be set only once? Doesn’t
this make them the equivalent of a constant?
There’s a method to the madness that makes perfect sense when analyzed. Because XSLT
relies on templates that can be called randomly, depending on the structure of the source XML
XML for ASP.NET Developers
262
document, the capability to update a particular global variable could introduce problems. These
types of problems are referred to as “side effects,” and the XSLT specification eliminates the
problem by allowing for no side effects. If you’ve ever stepped into a project that you didn’t
originally code that had bugs cropping up because of overuse of global variables, you’ll appre-
ciate this concept.
To illustrate the concept of side effects more, let’s take a look at a simple example. If a tem-
plate named template1 relies on another template named template2 to update the value of a
global variable, what happens if template2 doesn’t ever get processed? The rather obvious
answer is that the global variable will never be updated and therefore can cause potential prob-
lems to template1 when it is processed. By not allowing variables in XSLT style sheets to be
updated, this problem is avoided.
NOTE
Some XSLT processors do allow for variables to be updated by using extension func-
tions. These functions are specific to the XSLT processor and are not part of the XSLT
specification.
It’s important to realize that XSLT documents can be written without the use of variables.
However, variables can aid in cleaning up the code and can also result in more efficient XSLT
style sheets. The following example uses a portion of the code shown earlier in Listing 7.4 to
demonstrate how a variable declared globally (as a child element of the xsl:stylesheet ele-
ment) can be used in XSLT:
<xsl:variable name=”count” select=”count(//@handicap[. < 11])”/>
<xsl:template match=”golfer”>
</xsl:template>
Transforming XML with XSLT and ASP.NET
263
CHAPTER 7
The xsl:variable element can have a name and a select attribute. The name attribute is
required and serves the obvious purpose of assigning a name that can be used to reference
the variable. The select attribute is optional but when listed, must contain a valid XPath
expression.
So what have we gained by using a variable in this template? The code has actually been made
much more efficient as compared to writing the same code without using the variable. Instead
of having to calculate the count of all the golfers in the XML document with handicaps less
than 11 each time a golfer node template is matched, the variable obtains this value and stores
it when the XSLT style sheet is first loaded. The variable can then be referenced in several 7
places by adding the $ character to the beginning of the variable name ($count in this case).
Although the value of the color attribute found on the font tag could be added by using the
xsl:attribute element multiple times, by defining the value once in the variable, the code is
kept cleaner and the processing is more efficient.
TIP
The previous example shows a shortcut that can be used to embed data directly into
attributes that will be written out to the result tree. By wrapping a variable (or other
item) with the { and } brackets, it will dynamically be added to the result tree. This is
continues
XML for ASP.NET Developers
264
in contrast to adding the attribute name and value to a tag by using the
xsl:attribute element. As another example, if you had an attribute named width in
an XML document, you could write it out to a table tag by doing the following:
<table width=”{@width}”>. This is similar to doing something such as <table
width=”<%=myWidth%>”> in ASP.NET.
Variables can also be useful for storing values returned by calling a template. This process will
be shown a little later after you’re introduced to the xsl:param element.
Parameters in XSLT
Parameters are useful in a variety of programming languages, with XSLT being no exception.
Parameters can be used in XSLT documents in two basic ways. First, parameter values can be
passed in from an ASP.NET application. This allows data not found within the XML document
or XSLT style sheet to be part of the transformation process. Second, parameter values can be
passed between XSLT templates in much the same way that parameters can be passed between
functions in C# or VB.NET. You’ll see how parameters can be used in both ways later in the
chapter.
Declaring a parameter is similar to declaring a variable. Simply name the parameter and add an
optional select attribute:
<xsl:param name=”myParam” select=”’My Parameter Value’”/>
 
<xsl:value-of select=”lastName”/>
</xsl:template>
</xsl:stylesheet>
Having this parameter in the XSLT style sheet will cause a specific golfer’s information to be
transformed. Any other golfers in the XML document will simply be ignored. How is this
accomplished? A small change was made to the xsl:apply-templates element in the golfers
template. Instead of processing all golfer nodes in the XML document, the XPath expression
in the select attribute specifies the specific golfer node to process:
7
<xsl:apply-templates select=”//golfer[name/firstName=$golferName]”/>
Now imagine that GetOrders is the name of an XSLT template used to transform an XML
document containing customers and orders. Calling the template and passing the parameter can
be accomplished by doing the following:
<xsl:template match=”Customer”>
<xsl:call-template name=”GetOrders”>
<xsl:with-param name=”CustomerID” select=”@CustomerID”/>
</xsl:call-template>
</xsl:template>
<xsl:template name=”GetOrders”>
<xsl:param name=”CustomerID” select=”’ALFKI’”/>
<xsl:value-of select=”//Order[@CustomerID = $CustomerID]”/>
</xsl:template>
This example shows the use of the xsl:call-template and xsl:with-param elements to initi-
ate the template call. The xsl:call-template element has a single attribute that provides the
name of the template to call. The xsl:with-param element has two attributes. One is used to
name the parameter that data will be passed to and the other provides the value that is to be
passed. The select attribute can contain any valid XPath expression. The xsl:with-param
element can only be a child of the xsl:call-template or xsl:apply-templates element.
XML for ASP.NET Developers
266
TIP
The xsl:param element named CustomerID (shown earlier) has a parameter value of
ALFKI with single quotes around it because it is a string value rather than an XPath
expression. Had the single quotes been omitted, the XSLT processor would try to find
a node named ALFKI (which doesn’t exist, of course). Although this seems fairly obvi-
ous, it’s an easy mistake to make.
The xsl:param element in the template being called is updated by using the xsl:with-param
element shown previously. It has two potential attributes, including name and select, as
described earlier. In the previous example, the parameter named CustomerID is assigned a
default value of ALFKI. This value will be overridden when the GetOrders template is called
and a parameter value is passed to it using the xsl:with-param element.
Because variables cannot be updated in XSLT, parameters play a large role in allowing values
to be passed between templates. A global parameter (declared as a child of the xsl:stylesheet
or xsl:transform elements) can receive input from an ASP.NET page, but it cannot be
updated more than once after processing of the XSLT document begins. However, the capabil-
ity to place parameters within the scope of a specific template body offers the capability to call
templates recursively. This is possible because a single template can call itself and pass a para-
meter value (or more than one value, in the case of multiple parameters within the template)
that can then be processed as appropriate.
Although the inability to update variables and parameters may seem somewhat restrictive, the
authors of the XSLT specification knew that it was necessary because XML documents can
contain many different structures. You can’t depend on one template being processed before
another, especially in the case where one XSLT document is used to transform a variety of
XML documents—all with different structures.
</xsl:variable>
<b>Customer</b><br/>
ID: <xsl:value-of select=”$CID”/><br/>
<xsl:for-each select=”$Orders//Order”/>
<xsl:value-of select=”@OrderID”/>
</xsl:for-each>
</xsl:template>
<xsl:template name=”GetNames”>
<xsl:param name=”CustomerID” select=”’ALFKI’”/>
<xsl:value-of select=”//Orders[@CustomerID = $CustomerID]”/>
</xsl:template>
7
XSLT Functions
In Chapter 3, “XPath, XPointer, and XLink,” you were introduced to several functions built in
to the XPath language. Because XSLT relies on XPath for creating expressions that locate
nodes in an XML document, these functions are available for use in your XSLT documents. In
addition to these functions, the XSLT language adds a few more. Table 7.3 shows these func-
tions and provides a description and example of using them in XSLT documents.
href=”http://www.ilikegolf.com/golfers/golfers.xm
➥l”/>
<handicaps href=”handicapsWest.xml”/>
<handicaps href=”handicapsEast.xml”/>
</golfer>
</golfers>
xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”
version=”1.0”>
<xsl:template match=”/”>
<golfers>
<xsl:apply-templates
➥select=”//handicaps”/>
</golfers>
</xsl:template>
<xsl:template match=”handicaps”>
<xsl:copy-of
➥select=”document(@href)//handicap”/>
</xsl:template>
</xsl:stylesheet>
XML for ASP.NET Developers
270
system-property(string) This function returns the value of the system property iden-
tified by the name passed as the argument. Three different
system properties must be supported by a compliant XSLT
processor, including xsl:version, xsl:vendor, and
xsl:vendor-url. An example of using this function
follows:
<xsl:value-of select=”system-property
➥(‘xsl:version’)”/>
XML Stores
XSL/T XPath
XslTransform
XmlReader XmlWriter
System.IO.Stream System.IO.Textwriter
FIGURE 7.3
.NET Classes involved in XSL transformations.
AND ASP.NET
Because the XPathDocument class implements the IXPathNavigable
interface, it is able to leverage features built in to the abstract
XPathNavigator class (which, in turn, uses the XPathNodeIterator
abstract class for iteration over node-sets) to provide cursor-style
access to XML data, resulting in fast and efficient XSL transforma-
tions.
XslTransform The XslTransform class is used to transform XML data into other
structures. Using the XslTransform class involves instantiating it,
loading the proper style sheet with the Load() method, and then pass-
ing specific parameters to its Transform() method. This process will
be detailed in the next few sections.
XsltArgumentList The XsltArgumentList class is used to provide parameter values to
xsl:param elements defined in an XSLT style sheet. It can be passed
as an argument to the XslTransform class’s Transform() method.
Constructor Description
Public XPathDocument(XmlReader, Accepts an XmlReader as well as an
XmlSpace) XmlSpace enumeration.
Public XPathDocument(XmlReader) Accepts an XmlReader.
Public XPathDocument(TextReader) Accepts a TextReader.
Public XPathDocument(Stream) Accepts a Stream.
Public XPathDocument(string,XmlSpace) Accepts the string value of the path to
an XML document and an XmlSpace
enumeration.
Public XPathDocument(string) Accepts the string value of the path to an
XML document.
Listing 7.3 used the last constructor shown in Table 7.5 that accepts the path to the XML docu-
ment to transform. You could also load the XPathDocument with XML data contained in a
Stream (a FileStream for instance), an XmlReader, or a TextReader. Having these different
constructors offers you complete control over how transformations will be carried out in your
ASP.NET applications. Which one you use will depend on how you choose to access your
application’s XML documents. Listing 7.8 instantiates an XPathDocument class by passing in
an XmlTextReader object.
Running the code shown in Listing 7.5 will write out “XPathDocument successfully created!”
to the browser. You’ll certainly agree that because it has simply readied the XML document for
transformation, this code doesn’t buy you much. To actually transform the XML document 7
TRANSFORMING
AND ASP.NET
The XslTransform Class
The XslTransform class is found in the System.Xml.Xsl namespace. Using it is as easy as
instantiating it, loading the XSLT document, and then calling its Transform() method. Tables
7.6 and 7.7 show the different properties and methods found in the XslTransform class.
Property Description
XmlResolver The XmlResolver property can be used to specify a resolver class used
to resolve external resources. For example, it can be used to resolve
resources identified in xsl:include elements. If this property is not
set, the XslTransform class will use the relative path of the supplied
XSLT style sheet to resolve any included style sheets.
Chapter 5 showed an example of using the XmlUrlResolver class to
access authenticated documents.
Method Description
Load() Loads an XSLT document. This method can accept an XmlReader, a
document URL, or a variety of other objects.
Transform() The Transform() method is overloaded and can therefore accept a
variety of parameters. The most common form of the method that
you’ll likely use in your ASP.NET applications is shown next (check
the .NET SDK for the other overloaded versions of the method):
xsl.Transform(XpathDocument,XsltArgumentList,Stream)
XML for ASP.NET Developers
276
In the next section, you’ll see how you can pass in parameter values to XSLT style sheets using
the XsltArgumentList class.
XSLT parameters allow your ASP.NET applications to pass in values needed by the style sheet
to properly process the source XML document. In this section you’ll see how to create an
Transforming XML with XSLT and ASP.NET
277
CHAPTER 7
XsltArgumentList class and add parameter name/value pairs to it. It can also be used with
extension objects. Table 7.8 shows the different methods available on the XsltArgumentList
class (it has no properties).
Method Description
AddExtensionObject(namespaceURI, Allows an extension object to be added to the
object) collection of extension objects. The namespace
URI can be used to remove or retrieve an 7
object from the collection using either
The method that you’ll use most frequently among those listed in Table 7.8 is the AddParam()
method. This method accepts the name of the parameter, a namespace URI (optional), and the
value of the parameter. The following example shows how to add a parameter named
golferName to the XsltArgumentList collection:
XSLT Code:
<xsl:param name=”golferName”/>
ASP.NET Code:
XsltArgumentList args = new XsltArgumentList();
args.AddParam(“golferName”,””,”Dan”);
This code allows the XSLT parameter named golferName to be assigned a value of Dan.
Although the value of Dan was hard-coded into the AddParam() method, it could just as easily
be dynamically pulled from a text box or drop-down box, as you’ll see in the next example.
Because the XsltArgumentList class relies on the HashTable class behind the scenes, multiple
parameter name/value pairs can be added and stored.
After an XsltArgumentList class has been instantiated and filled with the proper name/value
pairs, how do the parameters in the XSLT style sheet get updated with the proper values? The
answer is to pass the XsltArgumentList into the XslTransform class’s Transform() method,
as shown next:
//Create the XPathDocument object
XPathDocument doc = new XPathDocument(Server.MapPath(“Listing7.1.xml”));
In the next section you’ll be presented with an ASP.NET application that does this task.
used along with the XPathDocument, XslTransform, and XsltArgumentList classes to display
the golfer’s information. Figures 7.4 and 7.5 show screen shots of the two pages involved in
the sample XSLT application.
FIGURE 7.5
The XSLT-generated results of the golfer selection.
To build this application, code-behind techniques were used in the ASP.NET page. If you’re
not familiar with this mechanism in ASP.NET coding, it allows the actual program code to be
stored separately from the visual portion (the HTML) found in the ASP.NET page. The tech-
nique of placing all the code (programming code and HTML) into one ASP.NET page shown
in many places throughout the book was used simply to make listings easier to read and follow.
XML for ASP.NET Developers
280
In practice, however, it’s highly recommended that you leverage code-behind techniques to
keep your ASP.NET code more maintainable.
For this example, a file named xsltGolfer.aspx.cs contains all the programming code for the
listings that follow, and xsltGolfer.aspx contains the HTML. The XSLT style sheet used for
the application is named xsltGolfer.xsl. Let’s start by examining what code is executed
when the ASP.NET page first loads (the Page_Load event). As you’ll see in Listing 7.10, this
code takes care of loading all the firstName element values found in the XML document into
a drop-down box.
You can see that the XmlTextReader and XmlNameTable classes are used to efficiently parse the
XML data and add it to the drop-down box (ddGolferName). Both classes were discussed in
Chapter 5, “Using the XmlTextReader and XmlTextWriter Classes in ASP.NET.”
After the user selects a specific golfer from the drop-down box and clicks the button, the
btnSubmit_Click event is fired. The code within this event takes care of getting the selected
golfer name value from the drop-down box and passes it into the XSLT style sheet by using the
XsltArgumentList class. The style sheet then takes care of transforming the selected golfer’s
XML data into HTML, as shown earlier in Figure 7.5. Listing 7.11 shows the code involved in
this process.
Transforming XML with XSLT and ASP.NET
281
CHAPTER 7
TRANSFORMING
AND ASP.NET
12: //Instantiate the XslTransform Classes
13: XslTransform transform = new XslTransform();
14: transform.Load(xslPath);
15:
16: //Add Parameters
17: XsltArgumentList args = new XsltArgumentList();
18: args.AddParam(“golferName”,””,
19: this.ddGolferName.SelectedItem.Value);
20:
21: //Call Transform() method
22: transform.Transform(doc, args, sw);
23:
24: //Hide-Show ASP.NET Panels in xsltGolfer.aspx
25: this.pnlSelectGolfer.Visible = false;
26: this.pnlTransformation.Visible = true;
27: this.divTransformation.InnerHtml = sb.ToString();
28: }
29: catch (Exception excp) {
30: Response.Write(excp.ToString());
31: }
32: finally {
33: xmlReader.Close();
34: sw.Close();
35: }
36: }
Although this doesn’t show much in the way of new classes, it does show how the different
classes discussed in earlier sections can be tied together to create an ASP.NET application that
leverages XML and XSLT.
XML for ASP.NET Developers
282
This class (named XsltDateTime) does nothing more than return the current system date and
time. It must be instantiated within an ASP.NET page and then added to the external object
collection of the XsltArgumentList class.
You may be wondering if it would be easier to pass the date and time into the style sheet using
a regular XSLT parameter. The answer is “yes”; it would be easier because no external objects
would be needed from within the style sheet. However, by calling the extension object from
within the XSLT style sheet, a more up-to-date date/time value will be returned (assuming that
accuracy matters in the application). And let’s face it, the demo code wouldn’t be as cool if a
regular XSLT parameter was used, especially because you know all about those at this point! 7
TRANSFORMING
AND ASP.NET
XsltArgumentList class. The lines of code relating to the extension object use are shown in
bold.
The XSLT style sheet obviously needs to be able to reference the XsltDateTime object that is
passed in and call its GetDateTime() method. This is accomplished by adding the proper
namespace prefix and URI into the style sheet. For this example, a namespace URI of
urn:xsltExtension-DateTime is used along with a namespace prefix of dateTimeObj. Any
namespace URI can be used as long as it is consistent between the ASP.NET page and the
XSLT style sheet. Listing 7.14 shows the complete style sheet and highlights where the exter-
nal object is referenced and used.
LISTING 7.14 Calling External Objects Within an XSLT Style Sheet (xsltExtension.xsl)
1: <?xml version=”1.0”?>
2: <xsl:stylesheet xmlns:xsl=”http://www.w3.org/1999/XSL/Transform”
3: xmlns:dateTimeObj=”urn:xsltExtension-DateTime” version=”1.0”>
4: <xsl:output method=”html” indent=”yes”/>
5: <xsl:param name=”golferName” select=”’Dan’”/>
6: <xsl:template match=”/”>
7: <xsl:apply-templates
8: select=”//golfer[name/firstName=$golferName]”/>
9: </xsl:template>
10: <xsl:template match=”golfers”>
11: <xsl:apply-templates select=”golfer”/>
12: </xsl:template>
13: <xsl:template match=”golfer”>
14: <table class=”borders” border=”0” width=”640” cellpadding=”4”
15: cellspacing=”0” bgcolor=”#efefef”>
16: <xsl:apply-templates select=”name”/>
17: <tr class=”blackText”>
Transforming XML with XSLT and ASP.NET
285
CHAPTER 7
TRANSFORMING
AND ASP.NET
29: </xsl:when>
30: <xsl:when test=”@skill=’poor’”>
31: color:#000000;
32: </xsl:when>
33: <xsl:otherwise>
34: color:#000000;
35: </xsl:otherwise>
36: </xsl:choose>
37: </xsl:attribute>
38: <xsl:value-of select=”@skill”/>
39: </td>
40: <td width=”12%” align=”left”>
41: <b>Handicap: </b>
42: </td>
43: <td width=”12%” align=”left”>
44: <xsl:value-of select=”@handicap”/>
45: </td>
46: <td width=”12%” align=”left”>
47: <b>Clubs: </b>
48: </td>
49: <td width=”40%” align=”left”>
50: <xsl:value-of select=”@clubs”/>
51: </td>
52: </tr>
53: <tr>
54: <td colspan=”6”> </td>
55: </tr>
56: <tr class=”blackText”>
57: <td colspan=”6” class=”largeBlackText”>
58: Favorite Courses
59: </td>
60: </tr>
61: <tr>
XML for ASP.NET Developers
286
How is the GetDateTime() method called on the XsltDateTime object that is passed into
the style sheet? This is accomplished by referencing the namespace prefix (dateTimeObj)
associated with the namespace URI assigned to the object in the ASP.NET page (urn:xslt
Extension-DateTime). The prefix is declared in line 3 and then used to call the GetDate
Time() method in line 75. Although this is a fairly straightforward example of using external
objects within XSLT style sheets, much more complicated functionality, such as querying data-
bases or calling Web services, can be accomplished with XSLT external objects.
Before this chapter ends, the next section will show how to create a reusable XSLT class that
can be used within ASP.NET applications and demonstrate the asp:Xml Web control. 7
18:
Having a reusable class for XSL transformations results in much cleaner ASP.NET code, as
Listing 7.16 shows.
Transforming XML with XSLT and ASP.NET
289
CHAPTER 7
TRANSFORMING
AND ASP.NET
//Fill the Hashtables with the params and external objects
xsltParams.Add(“golferName”,”Heedy”);
xsltObjects.Add(“urn:xsltExtension-DateTime”,xsltExtObj);
As this book was going to press, I wrote a new article demonstrating how the techniques
shown in Listing 7.15 can be used to create an ASP.NET server control geared at targeting
multiple devices using XML and XSLT. This article will appear in the premier issue
(September 2001) of Visual Studio Magazine (http://www.devx.com), and the code can be
downloaded from the DevX site or from the CodeBank section of
http://www.TomorrowsLearning.com.
In situations where the values of the DocumentSource and/or TransformSource attributes are
not known until runtime, they can be assigned dynamically, as Listing 7.18 shows.
LISTING 7.18 Dynamically Assigning Source Documents to the asp:Xml Web Control
1: <script language=”C#” runat=”server”>
2: void Page_Load(object sender, System.EventArgs e) {
3: xslTransform.DocumentSource = “Listing7.1.xml”;
4: xslTransform.TransformSource = “Listing7.2.xsl”;
5: }
6: </script>
7: <html>
8: <body>
9: <asp:Xml ID=”xslTransform” Runat=”server”></asp:Xml>
10: </body>
11: </html>
The asp:Xml server control also allows DOM structures to be passed into it programmatically
using the Document and Transform properties:
1: <%@Import Namespace=”System.Xml”%>
2: <%@Import Namespace=”System.Xml.Xsl”%>
3: <script language=”C#” runat=”server”>
4: void Page_Load(object sender, System.EventArgs e) {
5: XmlDocument doc = new XmlDocument();
6: doc.Load(Server.MapPath(“Listing7.1.xml”));
7: XslTransform trans = new XslTransform();
8: trans.Load(Server.MapPath(“Listing7.2.xsl”));
9: xslTransform.Document = doc;
10: xslTransform.Transform = trans;
11: }
12: </script>
13: <html>
14: <body>
15: <asp:Xml ID=”xslTransform” Runat=”server”>
16: </asp:Xml>
17: </body>
18: </html>
Transforming XML with XSLT and ASP.NET
291
CHAPTER 7
Summary
Although XSLT is a very big topic that can’t possibly be covered in a single chapter, you have
been exposed to some of the more important aspects of the language that will get you started
transforming XML documents in ASP.NET applications. XSLT provides a cross-platform,
language-independent solution that can used to transform XML documents into a variety of
structures.
The .NET platform provides several classes developed specifically for doing XSLT transforma-
tions, including the XslTransform and XPathDocument classes. By using these and other
7
classes, you have the ability to leverage the complete XSLT language in your ASP.NET
8
Features Using ASP.NET
IN THIS CHAPTER
• Introducing ADO.NET 294
Introducing ADO.NET
ADO.NET represents the next evolutionary step in working with data located in local or dis-
tributed data stores. Although its name is very similar to the ActiveX Data Objects (ADO) of
old, ADO.NET brings an abundance of new features to the table. In fact, ADO.NET has been
rewritten from the ground up to make working with all types of data (including data in the
form of XML) easier and more manageable. This chapter discusses many of ADO.NET’s new
features and shows you how straightforward it is to work with data structured as XML. If
you’re already familiar with “classic” ADO, you’re likely interested in knowing how it differs
from ADO.NET. Let’s start things off by comparing the two.
XML Integration
ADO.NET flips the scale as far as XML integration goes. No longer are you restricted to con-
verting a Recordset to XML using adPersistXml or some other manual means. In ADO.NET,
XML is a first-class citizen, and its integration is apparent from the start. What does this mean
for your ASP.NET projects? Quite simply, you no longer have to worry about which way you’d
like to work with data. If you would rather work with a standard relational view, you can. If
you need to work with XML natively, you can do that as well. Changing between these differ-
ent views can be accomplished with a call to a single property. We’ll talk more about this in a
moment.
called the DataSet. Although we’ll discuss the DataSet in more detail later in this chapter,
some of its new features include support for hierarchical relationships between tables, the
capability to add constraints, and much more. This means that you can now relate a database’s
Customers table to the Orders table all within the same object. This hierarchical association is
done without resorting to shape commands.
The RecordSet object also has another inherent problem that could haunt you, depending on the
context it was being used in. For example, if you attempt to pass a RecordSet between objects
living on opposite sides of a firewall, a potential problem could arise if the RecordSet was
blocked from passing through. With ADO.NET, this problem is alleviated because the DataSet
class is based in XML. Because XML can ride on top of protocols such as HTTP, exchanging
data records between objects living virtually anywhere in the world is now possible.
XML FEATURES
types exposed by COM marshaling. The elimination of type conversions results in an increase
ADO.NET’S
LEVERAGING
in scalability and efficiency.
Another important point that can be made about ADO.NET is that DataSets are always dis-
connected, which results in connections being released earlier. Looping through a DataSet can
be done without any connection being open to the data source. Although this can be done
using the disconnected RecordSet in ADO, disconnected Recordsets are still subject to the
inefficiencies found in COM marshaling, as mentioned earlier.
Although many other benefits are associated with ADO.NET, we’ll limit the scope of our dis-
cussions to topics relating specifically to ADO.NET, XML, and ASP.NET in this chapter.
However, if you have no experience working with ADO.NET, there’s no need to worry. The
next few sections cover enough of the basics to get you up to speed quickly. After the basics
are covered, you’ll see how XML can be used in conjunction with ADO.NET.
ADO.NET Basics
If you have experience working with “classic” ADO, you’ll be happy to know that much of
what you already know can be applied to ADO.NET. Although the RecordSet object no longer
exists, you’ll find that ADO.NET’s Connection, Command, and DataSet classes are still based
on many principles you’re already familiar with. One big change that you’ll see in the exam-
ples that follow is that ADO.NET is much more object oriented than ADO was. Even declaring
XML for ASP.NET Developers
296
a data type is accomplished through the use of objects. To prepare you for working with the
new ADO.NET classes, the next section covers the different managed providers that can be
used to connect to a data source.
Alternatively, the connection string can also be set using the ConnectionString property:
<%@ Import Name=”System.Data” %>
<%@ Import Name=”System.Data.SqlClient” %>
<script language=”C#” runat=”server”>
void Page_Load(Object Src, EventArgs E) {
string connStr = “server=localhost;uid=sa;pwd=;database=northwind”;
SqlConnection conn = new SqlConnection();
conn.ConnectionString = connStr; 8
Response.Write(“Connection established!”);
XML FEATURES
}
ADO.NET’S
LEVERAGING
</script>
As shown, the main class used in the preceding samples is the SQLConnection class.
Comparing the SQL to the OLE DB managed provider, you can see that each imports a differ-
ent namespace and uses the appropriate connection class to attach to a data source. Apart from
these differences, the code is virtually the same.
XML FEATURES
ADO.NET’S
LEVERAGING
7: try {
8: dataConn = new OleDbConnection(dbConnectString);
9: OleDbCommand oleDbCmd = new OleDbCommand(cmdString,dataConn);
10: dataConn.Open();
11: reader = oleDbCmd.ExecuteReader();
12: Response.Write(“<table><tr><td><b>ID</b></td>” +
13: “<td><b>Name</b></td></tr>”);
14: while (reader.Read()) {
15: Response.Write(“<tr>”);
16: Response.Write(“<td>” + reader[“CategoryID”].ToString() +
17: “</td>”);
18: Response.Write(“<td>” + reader[“CategoryName”].ToString() +
19: “</td>”);
20: Response.Write(“</tr>”);
21: }
22: Response.Write(“</table>”);
23: return “<p>OleDb Server Data Connection Opened”;
24: }
25: catch (Exception e) {
26: return(e.ToString());
27: }
28: finally {
29: if (reader != null) {
XML for ASP.NET Developers
300
Both examples exemplify passing the command string and a Connection object as arguments
to the appropriate Command class’s constructor. Several other constructors can be used to instan-
tiate a Command, but we’ll forgo that discussion because the .NET documentation details each
constructor well.
After the Command object has been created, the connection’s Open() method is called and the
appropriate Command is executed. Assuming that the following SQL string is passed to the
openConnection() method found in both listings, all fields existing in the Categories table
will be returned:
“SELECT * FROM Categories”
Having all the fields in the Categories table is certainly a step forward (you have to start
somewhere, right?), but what can we do with them? Both listings access the different fields by
using the DataReader class (SqlDataReader or OleDbDataReader). As each field is accessed,
information about it is written out to a Web page using the Response object.
Although little focus will be placed on the DataReader in this chapter, it’s important to under-
stand that because of the read-only, forward-only architecture it provides, the DataReader class
offers the best performance for your ASP.NET applications in cases where data simply need to
be displayed. The DataReader works in a manner similar to the XmlTextReader discussed in
Chapter 5, “Using the XmlTextReader and XmlTextWriter Classes in ASP.NET,” and is very
efficient when dealing with multiple records because it does not buffer a large amount of data
at any given time. Several other classes and ASP.NET objects can be used to work with data
returned from a data source, which you’ll learn about later in this chapter.
The code used with the ADO.NET Command class to access a stored procedure is much like the
code used to do the same using the Command object in ADO. A few differences exist in how
data types are declared, but for the most part the parameters passed into or out of a stored pro-
cedure are created much like they used to be in ADO. Listing 8.3 shows an example of access-
ing a stored procedure using the SQL managed provider.
XML FEATURES
ADO.NET’S
LEVERAGING
15:
16: SqlParameter param = sqlCmd.Parameters.Add(new
17: SqlParameter(paramName,SqlDbType.Char, 5));
18:
19: param.Direction = ParameterDirection.Input;
20: sqlCmd.Parameters[paramName].Value = paramValue;
21: dataConn.Open();
22: reader = sqlCmd.ExecuteReader();
23: Response.Write(“<table><tr><td><b>Product Name</b></td>”);
24: Response.Write(“<td><b>Total</b></td></tr>”);
25: while (reader.Read()) {
26: Response.Write(“<tr>”);
27: Response.Write(“<td>”+reader[“ProductName”].ToString());
28: Response.Write(“</td>”);
29: Response.Write(“<td>”+reader[“Total”].ToString());
30: Response.Write(“</td></tr>”);
31: }
32: Response.Write(“</table>”);
33: return “<p>Stored Procedure called successfully!”;
34: }
35: catch (Exception e) {
36: return(e.ToString());
37: }
XML for ASP.NET Developers
302
This example hits the Northwind sample database’s CustOrderHist stored procedure, which
accepts the customer ID as an input parameter. Although very limited in functionality (espe-
cially because it allows for only one parameter), the SqlConnect class takes care of executing
the CustOrderHist procedure by first setting the newly instantiated Command object’s
CommandType property to CommandType.StoredProcedure (line 14). The following types exist
in the CommandType enumeration:
• StoredProcedure
• TableDirect
• Text
After the CommandType is specified, lines 16–20 take care of adding the appropriate parameter
to be used with the stored procedure and specify its direction. You’ll notice that the data type
of the parameter uses another enumeration named SqlDbType rather than simply listing adChar
as in ADO. The SqlDbType enumeration contains the following data types:
BigInt NVarChar
Binary Real
Bit SmallDateTime
Char SmallInt
DateTime SmallMoney
Leveraging ADO.NET’s XML Features Using ASP.NET
303
CHAPTER 8
Decimal Text
Float TimeStamp
Image TinyInt
Int UniqueIdentifier
Money VarBinary
NChar VarChar
NText Variant
After the name, data type, and size of the parameter are created, the procedure’s value is set in
line 20 and the connection is opened. The remainder of the class uses the DataReader men-
tioned earlier in the chapter to write data out to the ASP.NET page. As previously mentioned,
the code to accomplish this task looks much like the code used to do the same in ADO, even
though ADO.NET introduces a few new classes and enumerations.
If you’re using the ADO managed provider, the type of the parameter can be set using the
OleDbType enumeration. This enumeration contains the following data types:
BigInt LongVarBinary
8
Binary LongVarChar
XML FEATURES
ADO.NET’S
LEVERAGING
Boolean LongVarWChar
BSTR Numeric
Char PropVariant
Currency Single
Date SmallInt
DBDate TinyInt
DBTime UnsignedBigInt
DBTimeStamp UnsignedInt
Decimal UnsignedSmallInt
Double UnsignedTinyInt
Empty VarBinary
Error VarChar
Filetime Variant
Guid VarNumeric
IDispatch VarWChar
Integer WChar
IUnknown
XML for ASP.NET Developers
304
The connection class shown in Listing 8.3 would look like Listing 8.4 when using the OLE DB
managed provider.
If you don’t feel like typing all the parameter information, you can always call the procedure
directly passing the appropriate parameters. This would be similar to calling the stored proce-
dure using the SQL Server Query Analyzer:
8
string cmdString = “exec CustOrderHist @customerID = ‘ALFKI’”;
XML FEATURES
SQLCommand sqlCmd = new SQLCommand(cmdString,dataConn);
ADO.NET’S
LEVERAGING
Working with Output and Return Values
If a stored procedure you’re working with passes back output or return values, the following
lines can be added as appropriate to the code shown in Listings 8.3 and 8.4 to handle these
values.
SQL Managed Provider Output Value:
param = sqlCmd.Parameters.Add(new SqlParameter(“@output”, SqlDbType.Int));
param.Direction = ParameterDirection.Output;
// ......Additional code here
Response.Write(sqlCmd.Parameters[“@output”].Value);
If you need to access a return value from a stored procedure, use code similar to the following
code segments:
SQL Managed Provider Return Value:
XML for ASP.NET Developers
306
The same thing can be accomplished with the OLE DB managed provider:
OleDbDataAdapter oleDbAdapter = new OleDbDataAdapter(cmdString,dataConn);
This code looks very similar to the code used to work with the command classes in Listings
8.1 and 8.2, doesn’t it? The different data adapter classes have constructors that accept the SQL
string to execute and the connection object. We won’t discuss them here, but several other con-
structors can be used, as well.
Leveraging ADO.NET’s XML Features Using ASP.NET
307
CHAPTER 8
The similarities between the command and data adapter classes stop when you take the
retrieved data and place it into a DataSet. Although the command classes have no methods for
filling a DataSet, the adapter classes expose the Fill() method. You’ll see this method in
action after the DataSet class has been defined more in the next section. The DataSet section
contains a discussion on how to work with data returned from a data source using XML.
The data adapter classes also allow you to define what stored procedures (or SQL string) to
use for doing inserts, updates, and deletes. This process occurs by assigning values to different
properties. After the properties have been set, you can call the Update() method on the appro-
priate data adapter class to execute the stored procedures or SQL strings. Any changes made to
the DataSet will then be updated in the data source, depending on what stored procedures you
specified. To see this in action, take a look at the following code. An example shown later in
the chapter shows how to use these in the context of a real application.
SqlDataAdapter sqlAdapter = new SqlDataAdapter(sql,conn);
// Insert Command
sqlAdapter.InsertCommand = new SqlCommand(“sp_InsertOrder”,conn);
sqlAdapter.InsertCommand.CommandType = CommandType.StoredProcedure;
// Update Command
sqlAdapter.UpdateCommand = new SqlCommand(“sp_UpdateOrder”,conn); 8
sqlAdapter.UpdateCommand.CommandType = CommandType.StoredProcedure;
XML FEATURES
// Delete Command
ADO.NET’S
LEVERAGING
sqlAdapter.DeleteCommand = new SqlCommand(“sp_DeleteOrder”,conn);
sqlAdapter.DeleteCommand.CommandType = CommandType.StoredProcedure;
After the stored procedures to use are established, the data adapter’s Update() method can
then be called. This will cause any records deleted from a DataSet to be deleted from the data
source. Likewise, any updates or inserts to the DataSet will also be updated or inserted, as
appropriate, into the data source.
Some of the stored procedures previously shown (sp_UpdateOrder, for instance) may require
that you pass in certain input parameters. For example, you may desire to update specific fields
in the data source rather than the entire row. Adding parameters to the appropriate insert,
update, or delete command can be accomplished by adding the parameter name, type and
value. The following code will set the @warehouse input parameter’s value:
sqlAdapter.UpdateCommand = new SqlCommand(“sp_UpdateOrder”,conn);
sqlAdapter.UpdateCommand.CommandType = CommandType.StoredProcedure;
sqlAdapter.UpdateCommand.Parameters.Add(new SqlParameter(“@warehouse”,
SqlDbType.NChar, 5));
sqlAdapter.UpdateCommand.Parameters[0].Value = “main”;
sqlAdapter.Update(DataSetObjectName);
Now that you’ve had a chance to get your feet wet with ADO.NET, let’s begin our discussion
on using ADO.NET and the DataSet class to work with XML data.
XML for ASP.NET Developers
308
The RecordSet is one of the main objects used to work with data in ADO. Using a RecordSet,
data can be selected from a data source and placed into an ASP page or manipulated using fil-
ters or sorts. Although the RecordSet object is certainly very useful, it does have a few limita-
tions. One of these limitations (discussed earlier in the chapter) is linked to the difficulty of
passing RecordSet objects through firewalls. Although poking a hole in a firewall may be an
option within your organization (if you have an extremely lenient LAN manager), exchanging
Recordsets with other companies becomes next to impossible with the presence of many
potential firewalls.
Another limitation is that RecordSets (even disconnected RecordSets) are subject to COM
marshaling, which creates inefficiencies as data types in the RecordSet are converted to COM
data types. In this world of distributed systems, the capability to support a wide range of data
types is paramount to interoperate with other systems and applications. The RecordSet object
also relies on a connection-based model by default and lacks integrated support for working
with XML and XML schemas.
The DataSet class strives to remedy some of the limitations found in the Recordset and add
additional functionality to enable distributed applications to communicate more effectively. The
following list details some of the improvements found in the DataSet class:
• The DataSet is completely disconnected and has no knowledge of the data source. All
communication with the data source is done through the managed provider.
• The DataSet can easily be viewed as an XML document and queried using XPath. This
XML basis means that it can pass through firewalls by riding on top of the HTTP proto-
col or be integrated into Web services.
• The DataSet allows multiple tables to be added, along with relationships and constraints
between the tables.
• Because DataSets are XML aware, more robust data types can be described, and ineffi-
ciencies associated with COM marshaling are eliminated.
• DataSets can be mapped to XML schemas that can be used to create an initial structure.
To better understand how DataSets can be used in ASP.NET applications, let’s take a look at a
sample shown in Listing 8.5 that uses the data adapter classes to fill a DataSet with data from
SQL Server’s Northwind database.
Leveraging ADO.NET’s XML Features Using ASP.NET
309
CHAPTER 8
XML FEATURES
ADO.NET’S
LEVERAGING
22: “</td>”);
23: Response.Write(“</tr>”);
24: }
25: Response.Write(“</table>”);
26: return “<p>Sql Server Data Connection Opened”;
27: }
28: catch (Exception e) {
29: return(e.ToString());
30: }
31: }
32: }
33:
34: public class OleDbConnect {
35:
36: public string OpenConnection(HttpResponse Response,
37: string dbConnectString,string cmdString) {
38: try {
39: DataSet dsOrders = new DataSet();
40: OleDbConnection dataConn =
41: new OleDbConnection(dbConnectString);
42: OleDbDataAdapter oleDbAdapter =
43: new OleDbDataAdapter(cmdString,dataConn);
44: oleDbAdapter.Fill(dsOrders,”Orders”);
XML for ASP.NET Developers
310
Listing 8.5 shows how both managed providers can be used to fill a DataSet object with data.
The discussion that follows will focus on the SQL managed provider code found in the
SqlConnect class.
When the ASP.NET page is first loaded, Page_Load is called, which takes care of instantiating
the SqlConnect class and calling its OpenConnection() (lines 68 and 69). This method accepts
the Response object, the connection string, and the SQL statement to execute against the data
source. Looking at line 11, you’ll notice that a DataSet class is instantiated and then filled
with the results of the SQL query. This is accomplished by calling the SqlDataAdapter’s
Fill() method (line 16).
Leveraging ADO.NET’s XML Features Using ASP.NET
311
CHAPTER 8
The Fill() method has several overrides. The one shown here passes the DataSet to fill and
the name of the table that is being filled in the DataSet (Orders in this example) as arguments.
Because only one table is being added to the DataSet, the second argument is optional.
Another version of the method allows the DataSet and table name to be passed as arguments
along with the record number to start with and the maximum number of records to include.
This allows you to say, “Fill the DataSet but start with row 25 and include the next 10 rows
that follow” by using this code:
sqlAdapter.Fill(dsOrders,25,10,”Orders”);
XML FEATURES
LISTING 8.6 Filling a DataSet with Multiple Tables
ADO.NET’S
LEVERAGING
1: <%@ Import Namespace=”System.Data”%>
2: <%@ Import Namespace=”System.Data.SqlClient”%>
3: <%@ Import Namespace=”System.Xml”%>
4: <%@ Import Namespace=”System.Text”%>
5: <html>
6: <script language=”C#” runat=”server”>
7: public class SqlConnect {
8: public DataSet ReturnDataSet(string dbConnectString,
9: string table1,string table2) {
10: DataSet dsTables = new DataSet();
11: dsTables.DataSetName = “CustomersData”;
12: SqlConnection dataConn = new SqlConnection(dbConnectString);
13: SqlDataAdapter customersAdapter =
14: new SqlDataAdapter(table1,dataConn);
15: SqlDataAdapter ordersAdapter =
16: new SqlDataAdapter(table2,dataConn);
17: customersAdapter.Fill(dsTables,”Customers”);
18: ordersAdapter.Fill(dsTables,”Orders”);
19: dsTables.Relations.Add(“CustomerOrders”,
20: dsTables.Tables[“Customers”].Columns[“CustomerId”],
21: dsTables.Tables[“Orders”].Columns[“CustomerId”]);
XML for ASP.NET Developers
312
XML FEATURES
ADO.NET’S
LEVERAGING
86: </head>
87: <body bgcolor=”#ffffff”>
88: <h2>
89: <b>Walking Multiple DataSet Tables</b>
90: </h2>
91: <div id=”content” runat=”server” />
92: <p>
93:
94: </p>
95: <b>XML Data:</b>
96: <br />
97: <asp:TextBox ID=”xml” Runat=”server” Columns=”75” Rows=”15”
98: TextMode=”MultiLine” />
99: </body>
100: </html>
This example shows how you can walk relationships between tables. It also gives a quick
glimpse into how easy it is to view data held within a DataSet as XML.
When the page first loads, a class named SqlConnect is instantiated and its ReturnDataSet()
method is called (lines 34–36). The ReturnDataSet() method accepts a connection string and
XML for ASP.NET Developers
314
two SQL queries as arguments. The method works by first creating a DataSet object named
dsTables (line 10). SqlConnection and SqlDataAdapter objects are then created and the
SqlDataAdapter objects are used to query the database and hold the results of data from two
SQL queries passed into the method (lines 13–16).
Two tables within the DataSet (dsTables) are then filled with data from the two data adapters
by calling each data adapter’s Fill() method (lines 17 and 18). This method has several over-
rides that can be used. When the two tables within the DataSet are filled, a relationship is
established between the tables, using the Relations property (lines 19–21). This associates
both tables based on their CustomerID fields and makes it possible to create a parent/child rela-
tionship within the DataSet. After all this has been done, the DataSet is returned to the caller
of the ReturnDataSet method.
When the filled DataSet is ready to use, each row of the Customers table (within the DataSet)
is looped through using a foreach loop (lines 38–68). Before moving to the next row, the cur-
rent CustomerID is used to find all the orders for the appropriate customer in the Orders table
of the DataSet. These rows are then looped through with an additional foreach loop (lines
58–63). The entire process adds data to a StringBuilder class named output that is written
out to the ASP.NET page on completion of the looping process.
Line 70 shows one potential way of viewing the data held within the DataSet as XML. It cre-
ates an XmlDataDocument class and passes in the DataSet (TablesDataSet in this example) to
its constructor. The XmlDataDocument class extends the XmlDocument class detailed in Chapter
6, “Programming the Document Object Model (DOM) with ASP.NET,” and provides a way to
work with data using the Document Object Model (DOM). This is a very powerful feature
because it means you can perform XPath queries or do XSLT transformations directly on data
received from a data source. You’ll see how to use this class in greater detail later in the
chapter.
The end result of the code in Listing 8.6 is a page that allows the end user to see what orders
each customer has placed without having to leave the page. A screen shot of this is shown in
Figure 8.1.
The next sample (Listing 8.7) follows along the lines of the previous example, except that it
uses one of the DataSet’s methods to access the DataSet as XML rather than creating an
XmlDataDocument object. The resulting XML document is placed into an XML Data Island in
the browser and queried on the client side using XPath.
Leveraging ADO.NET’s XML Features Using ASP.NET
315
CHAPTER 8
FIGURE 8.1.
Relating the Customers and Orders tables to create an ASP.NET page. 8
XML FEATURES
ADO.NET’S
LEVERAGING
LISTING 8.7 Viewing a DataSet as XML
1: <%@ Import Namespace=”System.Data”%>
2: <%@ Import Namespace=”System.Data.SqlClient”%>
3: <%@ Import Namespace=”System.Xml”%>
4: <script language=”C#” runat=”server”>
5: public class SqlConnect {
6: public DataSet ReturnDataSet(string dbConnectString,string table1,
7: string table2,string key1, string key2) {
8: DataSet dsTables = new DataSet();
9: SqlConnection dataConn = new SqlConnection(dbConnectString);
10: SqlDataAdapter dsCmdCustomers =
11: new SqlDataAdapter(table1,dataConn);
12: SqlDataAdapter dsCmdOrders =
13: new SqlDataAdapter(table2,dataConn);
14: dsCmdCustomers.Fill(dsTables,”Table1”);
15: dsCmdOrders.Fill(dsTables,”Table2”);
16: dsTables.Relations.Add(“Relation1”,
17: dsTables.Tables[“table1”].Columns[key1],
18: dsTables.Tables[“table2”].Columns[key2]);
XML for ASP.NET Developers
316
This example shows another way that data within a DataSet can be viewed as XML. The
SqlConnect class functions much the same as the SqlConnect class shown in Listing 8.6. After
calling this class and getting a DataSet back, the DataSet’s GetXml() method is called and the
resulting XML data is placed within an XML Data Island in the ASP.NET page. Calling the
DataSet’s GetXml() method yields a string containing the XML schema and XML data
retrieved from the Northwind database tables. Alternatively, if you want only the XML
schema, you can call the GetXmlSchema() method.
After this data has been added to the ASP.NET page, client-side script takes care of executing
a few XPath statements to determine the total number of customers and orders contained
within the XML Data Island (lines 46 and 47). Doing this allows some of the processing bur-
den to be removed from the server and placed on the client. Although the example is very sim-
ple, the client-side code could bind tables to the XML Data Island to allow records to be
updated, deleted, or inserted. The data found within the XML Data Island could then be posted
8
XML FEATURES
back to the server and the original data store (or another) could be updated. You’ll see how
ADO.NET’S
LEVERAGING
XML data can be read into a DataSet later in the chapter. Figure 8.2 shows the results that the
code in Listing 8.7 returns.
FIGURE 8.2.
Passing data within a DataSet to the browser as XML.
XML for ASP.NET Developers
318
XML FEATURES
ADO.NET’S
LEVERAGING
45: CellPadding=”5”
46: CellSpacing=”0”
47: Font-Name=”Arial”
48: Font-Size=”8pt”
49: HeaderStyle-BackColor=”#6C0A00”
50: HeaderStyle-ForeColor=”#ffffff”
51: EnableViewState=”false”
52: OnSortCommand=”OrdersDataGrid_Sort”
53: AllowSorting=”true”
54: />
55: </form>
56: </body>
57: </html>
The ReadXml() method reads in an XML document’s schema and data. Line 12 shows how the
method can be used to read from a stream. To read in only the schema associated with an
XML document, the ReadXmlSchema() method can be called when working with the DataSet
class. The capability to dynamically create the structure of a DataSet class (including tables,
relationships, and so on) based on an XML schema opens up a new set of possibilities for
working with data in a disconnected manner.
XML for ASP.NET Developers
320
Listing 8.10 shows the results of calling the WriteXml() method on the DataSet just created.
XML FEATURES
19: <ContactTitle>Owner</ContactTitle>
ADO.NET’S
LEVERAGING
20: <Address>Avda. de la Constitución 2222</Address>
21: <City>México D.F.</City>
22: <PostalCode>05021</PostalCode>
23: <Country>Mexico</Country>
24: <Phone>(5) 555-4729</Phone>
25: <Fax>(5) 555-3745</Fax>
26: </Customers>
27:
28: <!-- More Customers nodes follow -->
29:
30: <Orders>
31: <OrderID>11076</OrderID>
32: <CustomerID>BONAP</CustomerID>
33: <EmployeeID>4</EmployeeID>
34: <OrderDate>1998-05-06T07:00:00</OrderDate>
35: <RequiredDate>1998-06-03T07:00:00</RequiredDate>
36: <ShipVia>2</ShipVia>
37: <Freight>38.28</Freight>
38: <ShipName>Bon app’</ShipName>
39: <ShipAddress>12, rue des Bouchers</ShipAddress>
40: <ShipCity>Marseille</ShipCity>
41: <ShipPostalCode>13008</ShipPostalCode>
42: <ShipCountry>France</ShipCountry>
43: </Orders>
XML for ASP.NET Developers
322
Listing 8.11 shows what a schema generated by calling the DataSet’s WriteXmlSchema()
method looks like. This schema follows the standards found in the W3C schema specification
discussed in Chapter 4, “Understanding DTDs and XML Schemas”.
XML FEATURES
ADO.NET’S
LEVERAGING
39: <xsd:complexType>
40: <xsd:sequence>
41: <xsd:element name=”OrderID” type=”xsd:int”
42: minOccurs=”0” />
43: <xsd:element name=”CustomerID” type=”xsd:string”
44: minOccurs=”0” />
45: <xsd:element name=”EmployeeID” type=”xsd:int”
46: minOccurs=”0” />
47: <xsd:element name=”OrderDate” type=”xsd:dateTime”
48: minOccurs=”0” />
49: <xsd:element name=”RequiredDate” type=”xsd:dateTime”
50: minOccurs=”0” />
51: <xsd:element name=”ShippedDate” type=”xsd:dateTime”
52: minOccurs=”0” />
53: <xsd:element name=”ShipVia” type=”xsd:int”
54: minOccurs=”0” />
55: <xsd:element name=”Freight” type=”xsd:decimal”
56: minOccurs=”0” />
57: <xsd:element name=”ShipName” type=”xsd:string”
58: minOccurs=”0” />
59: <xsd:element name=”ShipAddress” type=”xsd:string”
60: minOccurs=”0” />
61: <xsd:element name=”ShipCity” type=”xsd:string”
XML for ASP.NET Developers
324
Although the DataSet has many more features that haven’t been covered, you’ve been exposed
to a few of the different ways it can be used to work with relational and XML data formats.
The next section shows how the DataSet class can further be extended to work with XML data
using DOM properties and methods.
XPath statements and can be transformed using XSLT. How is this accomplished? The answer
lies with the XmlDataDocument class that you were briefly introduced to earlier. This class is
designed to work hand-in-hand with the DataSet class to allow you to manipulate data using
regular XML-style programming. It can be thought of as a DataSet-aware XmlDocument.
Figure 8.3 shows the relationship between the DataSet and XmlDataDocument classes in the
.NET framework.
XmlDataDocument DataSet
Sync
8
XmlReader Managed Provider XmlReader
XML FEATURES
ADO.NET’S
XmlTextReader XmlNodeReader XmlTextReader XmlNodeReader
LEVERAGING
FIGURE 8.3.
Passing data within a DataSet to the browser as XML.
The most important concept shown in Figure 8.3 is that changes made to a DataSet are auto-
matically made in the corresponding XmlDataDocument as well. This automatic syncing
between the classes gives you the choice of working with data as XML or as a standard rela-
tional view! However, syncing between the XmlDataDocument and DataSet classes may not be
possible in cases where a node is added to the DOM tree that does not fit into a row found in
the DataSet’s relational structure. For complete syncing to occur, nodes added into the DOM
tree must match up with a row in the DataSet.
To see the XmlDataDocument in action, Listing 8.12 contains an example that utilizes code
shown earlier in Chapter 6. The code simply walks through the XML and displays it in the
browser.
XML FEATURES
ADO.NET’S
LEVERAGING
71: “</font><br>”;
72: } else {
73: if (node.Attributes.Count > 0) {
74: XmlNamedNodeMap oNamedNodeMap = node.Attributes;
75: output += “<b>” + node.Name + “</b> (“;
76: foreach (XmlAttribute att in oNamedNodeMap) {
77: if (h!=0) output += “ ”;
78: h++;
79: output += “<i>” + att.Name + “</i>=\”” +
80: att.Value + “\””;
81: }
82: output += “)<br>\n\n”;
83: } else {
84: output += “<b>” + node.Name + “</b><br>\n\n”;
85: }
86: } // end if
87: } // writeNodeName
88: } //WriteXmlText
89:
90: public void Page_Load(Object Src, EventArgs E) {
91: String connStr = “server=localhost;uid=sa;pwd=;database=Northwind”;
92: String table1 = @”SELECT * FROM Customers WHERE CustomerID LIKE ‘d%’
93: ORDER BY ContactName”;
XML for ASP.NET Developers
328
Much of what you’ve seen up to this point in the book takes place in the preceding sample.
A DataSet is filled with two tables using the SQL managed provider, and a relationship
between the tables is established. After this process occurs, an XmlDataDocument class is
instantiated and is passed the newly created DataSet named ds (line 101). This allows the data
within the DataSet to be treated like data found within an XmlDocument. To show this, the
XmlDataDocument (xmlDataDoc) is first normalized to organize the elements properly, and it
is then passed into the WriteXmlText class’s ParseDoc() method. This class loops through
all the child nodes and creates an HTML representation of the data contained within the
XmlDataDocument object. For more information on the code contained in the ParseDoc()
method, refer back to Chapter 6.
Listing 8.13 provides a lot of code aimed at showing how the DataSet and XmlDataDocument
classes can be used interchangeably. The goal of the sample is to show you how both are
synced and to show several things you can do with each class. Although it does not show the
most efficient way to work with data (its goal is to show many facets of ADO.NET), it does
provide a good glimpse of the flexibility ADO.NET provides in moving XML data to and from
a data source.
XML FEATURES
ADO.NET’S
LEVERAGING
3: <%@ Import Namespace=”System.Xml”%>
4:
5: <script language=”C#” runat=”server”>
6: public class SqlConnect {
7: public DataSet ReturnDataSet(string dbConnectString,
8: string table1,string table2,
9: string key1,string key2) {
10: DataSet dsTables = new DataSet();
11: SqlConnection dataConn = new SqlConnection(dbConnectString);
12: SqlDataAdapter dsCmdCustomers =
13: new SqlDataAdapter(table1,dataConn);
14: SqlDataAdapter dsCmdOrders =
15: new SqlDataAdapter(table2,dataConn);
16: dataConn.Open();
17: dsCmdCustomers.Fill(dsTables,”Customers”);
18: dsCmdOrders.Fill(dsTables,”Orders”);
19: dsTables.Relations.Add(“Relation1”,
20: dsTables.Tables[“Customers”].Columns[key1],
21: dsTables.Tables[“Orders”].Columns[key2]);
22: return(dsTables);
23: }
24: public int InsertDataSet(string dbConnectString,DataSet ds) {
XML for ASP.NET Developers
330
XML FEATURES
ADO.NET’S
LEVERAGING
90: “ CustomerID LIKE ‘d%’”;
91: String output = “”;
92: //****** Save Schema to a File/Save XML data to a File
93: SqlConnect Sqlconn = new SqlConnect();
94: DataSet ds = Sqlconn.ReturnDataSet(connStr,customersSql,
95: ordersSql,
96: “CustomerID”,”CustomerID”);
97: ds.WriteXmlSchema(Server.MapPath(“schema.xsd”));
98: ds.WriteXml(Server.MapPath(“customers.xml”),
99: XmlWriteMode.IgnoreSchema);
100:
101: //****** Load the schema.xsd file into an empty DataSet object
102: DataSet newDS = new DataSet();
103: newDS.ReadXmlSchema(Server.MapPath(“schema.xsd”));
104: newDS.ReadXml(Server.MapPath(“customers.xml”));
105: newDS.AcceptChanges();
106: XmlDataDocument xmlDataDoc = new XmlDataDocument(newDS);
107:
108: //****** Add a new row and fill 4 columns with data
109: DataRow row = newDS.Tables[“Customers”].NewRow();
110: row[“CustomerID”] = “DLWID”;
111: row[“CompanyName”] = “Tomorrows Learning”;
112: row[“ContactName”] = “Dan Wahlin”;
XML for ASP.NET Developers
332
XML FEATURES
ADO.NET’S
LEVERAGING
177: <html>
178: <body bgcolor=”#ffffff”>
179: <div id=”divChanges” runat=”server” />
180: <h2>
181: Data Before Submitting Insert to DataBase
182: </h2>
183: <ASP:DataGrid id=”BeforeDataGrid” runat=”server”
184: Width=”700” BackColor=”#E6E6CC” BorderColor=”#000000”
185: ShowFooter=”false” CellPadding=5 CellSpacing=”0”
186: Font-Name=”Arial” Font-Size=”8pt”
187: HeaderStyle-BackColor=”#6C0A00”
188: HeaderStyle-ForeColor=”#ffffff” EnableViewState=”false”
189: />
190: <br>
191: <h2>
192: Data After Being Submitted to DataBase
193: </h2>
194: <ASP:DataGrid id=”AfterDataGrid” runat=”server”
195: Width=”700” BackColor=”#E6E6CC” BorderColor=”#000000”
196: ShowFooter=”false” CellPadding=5 CellSpacing=”0”
197: Font-Name=”Arial” Font-Size=”8pt”
198: HeaderStyle-BackColor=”#6C0A00”
199: HeaderStyle-ForeColor=”#ffffff” EnableViewState=”false”
200: />
XML for ASP.NET Developers
334
Execution of this code results in three different DataGrid WebControls being displayed in the
browser, as shown in Figures 8.4, 8.5, and 8.6.
FIGURE 8.4.
State of the data after adding a single row but before being inserted into the database.
The example uses one main class (SqlConnect) to select, insert, and delete data from the
Northwind database. The code starts by calling this class’s ReturnDataSet() method, which
fills a DataSet with data. After filling the DataSet, the XML schema and XML data are writ-
ten out to two files named schema.xsd and customers.xml. From here, a new DataSet (named
newDS) is created and the data that was saved previously to the two files is read back in (lines
103 and 104). In line 105, the DataSet’s AcceptChanges() method is called so that the rows
read in from the customers.xml file aren’t treated as new rows. This is necessary to prevent
the rows from being reinserted into the database later in the code.
Leveraging ADO.NET’s XML Features Using ASP.NET
335
CHAPTER 8
FIGURE 8.5.
State of the data after inserting a single row into the database.
XML FEATURES
ADO.NET’S
LEVERAGING
FIGURE 8.6.
State of the data after deleting a single row from the database.
Line 106 shows the creation of an XmlDataDocument object named xmlDataDoc. This object is
associated with newDS (the DataSet created earlier). Later in the code, you’ll see that
xmlDataDoc will automatically be synced with newDS.
Lines 108–126 take care of adding a new row to newDS. While the row is being added, a simple
check is done to ensure that the DataSet’s schema is indeed validating individual column
entries (lines 116–121). The code attempts to enter data into a dummy column named “test”
as shown next:
115: //****** Let’s see how well the schema validates
116: try {
117: row[“test”] = “Testing Schema”;
XML for ASP.NET Developers
336
118: }
119: catch (Exception exc) {
120: //column[“test”] doesn’t really exist
121: }
If you remove the try…catch block, you’ll see that the DataSet’s schema does ensure that
the column exists before allowing any entries. It will not allow bogus columns or data to be
entered into the DataSet. This validation offers the advantage of being able to check that data
is correct before any attempt to interact with the data source is made.
After this new row is added, the changes to the DataSet can be viewed by calling the
GetChanges() method. This method returns a DataSet containing the changed records. If the
changes need to be viewed as XML, the DataSet’s GetXml() method can be called.
At this point, the new row now exists in the DataSet. Before the record is updated in the data-
base, the XPathDocument (named xmlDataDoc) created earlier runs a few checks on the data it
contains using XPath statements. The code for this is shown next:
129: //****** Show that the DataSet and XmlDataDocument are synced
130: XmlNode node = xmlDataDoc.SelectSingleNode
➥(“//CustomerID[.=’DLWID’]”);
131: if (node != null) {
132: Response.Write(“<b>XmlDataDocument Synced with” +
133: “ DataSet!</b><br />”);
134: Response.Write(“Newly added CustomerID in XmlDataDocument: “ +
135: node.InnerText);
136: }
The newly added row (a node in the XmlDataDocument) is selected by using the
SelectSingleNode() method (line 130). The XPath statement instructs this method to find the
CustomerID node that is equal to “DLWID.” By running the code, you’ll see that this node is
found and its value is written out to the page. This shows that that the DataSet and
XmlDataDocument classes are synced for you automatically without any intervention on your
part!
After it has been verified that the DataSet and XmlDataDocument have been synced, a call is
made to the XmlDataDocument’s GetElementFromRow() method (line 138).
138: //****** Use the XmlDataDocument’s GetElementFromRow() Method
139: XmlElement customer = xmlDataDoc.GetElementFromRow(
140: newDS.Tables[“Customers”].Rows[2]);
141: Response.Write(“<br />CompanyName: “ +
142:
customer.SelectSingleNode(“CompanyName”).InnerText);
This method will find a specific node in the DOM structure based on a row in the DataSet.
Row numbering in DataSets starts with 0, and because only two rows initially existed in
Leveraging ADO.NET’s XML Features Using ASP.NET
337
CHAPTER 8
newDS, the newly added row’s position will be 2. Instead of hard-coding this value, the last row
in the DataSet could be found programmatically, as well.
Now that the new row has been added to newDS and synchronization between it and
xmlDataDoc has been verified, the DataSet can be bound to a DataGrid in the ASP.NET page
and then updated in the database:
144: BeforeDataGrid.DataSource =
145: newDS.Tables[“Customers”].DefaultView;
146: BeforeDataGrid.DataBind();
147:
148: //****** Insert the Record in the Database
149: Sqlconn.InsertDataSet(connStr,newDS);
Because the DataSet knows nothing about where the data came from, the code calls the
SqlConnect class’s InsertDataSet() method (line 149). This method creates a
SqlDataAdapter object and specifies what Insert statement to use to move the newly added
row in the DataSet to the Northwind database (lines 29–62). The wonderful thing about speci-
fying what command to use (insert, delete, or update), is that the SqlDataAdapter takes care of
executing the command automatically. Had the code inserted 10 rows, all these changes would
have been moved to the Northwind database automatically. Talk about your time-savers! 8
XML FEATURES
Continuing with the example in Listing 8.13, after the insert has been completed, a call is
ADO.NET’S
LEVERAGING
again made to the SqlConnect class’s ReturnDataSet() method, and the resulting DataSet is
bound to another DataGrid (lines 151–154). This is done to show that the new row was indeed
inserted into the Northwind database.
151: ds = Sqlconn.ReturnDataSet(connStr,customersSql,ordersSql,
152: “CustomerID”,”CustomerID”);
153: AfterDataGrid.DataSource = ds.Tables[“Customers”].DefaultView;
154: AfterDataGrid.DataBind();
The next section of code (lines 156–172) loads the DataSet (named ds) into a new
XmlDataDocument. The code uses the XmlDataDocument’s SelectSingleNode() and
RemoveChild() methods to find and remove the node having a value of “DLWID.” After the
node has been removed, the SqlConnect’s DeleteDataSet() method is called, which accepts
the DataSet containing the row(s) to be deleted as its second argument. To accommodate this,
the original DataSet (ds) could have been passed in because the XmlDataDocument node dele-
tion is synced. However, line 166 shows how a DataSet can be passed directly using the
XmlDataDocument’s DataSet property:
The code in Listing 8.13 has shown how the DataSet and XmlDataDocument classes can make
it very easy to view data both in a relational fashion and as XML. Data retrieved from a data-
base can be queried via XPath statements, and data from an XML document can be treated as
relational data. The XmlDataDocument and DataSet classes also allow XML documents coming
from any source or location to be added to a different data source without requiring the XML
document to be manually parsed. As long as the document follows the rules outlined in the
DataSet schema, these two classes can use it to update a database or any other source.
It’s important to reemphasize that this code does not show the most efficient way to work with
data in your ASP.NET applications. It’s unlikely that you would use both the DataSet and
XmlDataDocument classes in every data-centric ASP.NET application. The example was
designed to make you aware of the key features offered by ADO.NET so that you can use the
features appropriately.
The ColumnMapping property can be set equal to one of four MappingType enumeration values,
depending on what XML structure you desire. The different enumerations are listed in Table 8.3.
Listing 8.14 shows how a table can be loaded with a few rows of data. The code allows most
columns to default to XML elements but programmatically sets one column (named
CustomerID) to be an attribute.
XML FEATURES
2: <%@ Import Namespace=”System.Data.SqlClient”%>
ADO.NET’S
LEVERAGING
3: <%@ Import Namespace=”System.Xml”%>
4: <%@ Import Namespace=”System.IO”%>
5: <script language=”C#” runat=”server”>
6: public void Page_Load(Object Src, EventArgs E) {
7: DataSet ds = new DataSet();
8: DataTable tblCustomers = new DataTable(“Customers”);
9:
10: DataColumn customerID = new DataColumn(“CustomerID”);
11: customerID.DataType = System.Type.GetType(“System.String”);
12: customerID.ColumnMapping = MappingType.Attribute;
13: customerID.Unique = true;
14: tblCustomers.Columns.Add(customerID);
15:
16: DataColumn companyName = new DataColumn(“CompanyName”);
17: companyName.DataType = System.Type.GetType(“System.String”);
18: companyName.Unique = false;
19: tblCustomers.Columns.Add(companyName);
20:
21: DataColumn contactName = new DataColumn(“ContactName”);
22: contactName.DataType = System.Type.GetType(“System.String”);
23: contactName.Unique = false;
24: tblCustomers.Columns.Add(contactName);
25:
XML for ASP.NET Developers
340
On executing the preceding code, the DataSet emits the following XML. As shown next, the
CustomerID field’s data is contained within an attribute:
<NewDataSet>
<Customers CustomerID=”DLWID”>
<CompanyName>Wahlin Consulting</CompanyName>
<ContactName>Dan Wahlin</ContactName>
<ContactTitle>Programmer/Author</ContactTitle>
</Customers>
</NewDataSet>
XML FEATURES
ADO.NET’S
LEVERAGING
schemas. Why would you want to load an XML schema in the first place? One potential
answer is that it can save you a lot of work!
When a DataSet is first created, it is completely empty. To be able to add rows of data to it,
tables with columns must be programmatically added first, as was shown earlier in Listing
8.14. By loading a schema from a file, tables and columns are loaded for you along with their
appropriate data types. Also, any relationships specified between tables are also loaded for you.
This means that you can easily add new records and then persist them to a data source at a
later time. It also means that a schema can be used in different capacities. It can be used to val-
idate an XML document or to create the shell structure for a DataSet table (or tables). Listing
8.15 shows how an XML schema can be used to provide the shell structure for a DataSet
using the DataSet’s ReadXmlSchema(). After this structure is created, rows of data can be
added to the table.
Line 10 takes care of creating the structure that the DataSet should follow for you. After the
structure has been established using the schema, rows can be added and saved to a remote data
source. In cases where an XML document containing the new records needs to be returned to
the end user’s application, the DataSet can return an XML document as shown earlier. This is
a very powerful feature that can be leveraged when working with Web services. We’ll discuss
these more in Chapter 10, “Working with ASP.NET, XML, SOAP, and Web Services.”
At times, you may need to use XML data contained within attributes rather than elements. For
example, you may want to use the XML schema shown next to create the correct DataSet
structure for a given ASP.NET application.
<xsd:schema xmlns:msdata=”urn:schemas-microsoft-com:xml-msdata”
xmlns:xsd=”http://www.w3.org/2001/XMLSchema” id=”Xml”>
<xsd:element name=”Customers”>
<xsd:complexType>
8
<xsd:attribute name=”CustomerID” type=”xsd:string” />
XML FEATURES
ADO.NET’S
LEVERAGING
<xsd:attribute name=”CompanyName” type=”xsd:string” />
<xsd:attribute name=”ContactName” type=”xsd:string” />
<xsd:attribute name=”ContactTitle” type=”xsd:string” />
<xsd:attribute name=”Address” type=”xsd:string” />
<xsd:attribute name=”City” type=”xsd:string” />
<xsd:attribute name=”PostalCode” type=”xsd:string” />
<xsd:attribute name=”Country” type=”xsd:string” />
<xsd:attribute name=”Phone” type=”xsd:string” />
<xsd:attribute name=”Fax” type=”xsd:string” />
</xsd:complexType>
</xsd:element>
</xsd:schema>
Fortunately, ADO.NET DataSets allow this schema to be loaded using the ReadXmlSchema()
method shown earlier. After the schema is loaded, new rows can be added like normal. Calls
made to the DataSet’s GetXml() or WriteXml() methods will output an XML document with
the newly added data being contained within attributes rather than elements.
DataSet can be used to “infer” what the table structure of the DataSet should look like. The
process allows the XML document that contains the proper structure to be read through by the
DataSet. The DataSet then creates a representative XML schema dynamically. Listing 8.16
shows an example of this. Line 10 contains the code used to load the XML document and then
infer its schema for use within the DataSet.
The InferXmlSchema() method has several overrides that can accept a file path, Stream,
TextWriter, or XmlReader. Each of these overrides accepts an array of namespace URI strings
that should be excluded when the DataSet is inferring the XML schema from an XML docu-
ment. This allows virtually any XML document to be used as a “model” for the DataSet’s
schema, even if it contains undesirable namespaces.
8
Using DataSets to Work with Hierarchical XML
XML FEATURES
ADO.NET’S
LEVERAGING
Data and XSLT
Up to this point, all data returned from a database and viewed as XML has been kept in sepa-
rate XML structures. For example, when the Customers and Orders tables were queried in the
Northwind database, all the XML data was kept in two element node-sets as shown next:
<Customers>
<CustomerID>DUMON</CustomerID>
<CompanyName>Du monde entier</CompanyName>
<ContactName>Janine Labrune</ContactName>
<ContactTitle>Owner</ContactTitle>
<Address>67, rue des Cinquante Otages</Address>
<City>Nantes</City>
<PostalCode>44000</PostalCode>
<Country>France</Country>
<Phone>40.67.88.88</Phone>
<Fax>40.67.89.89</Fax>
</Customers>
<Orders>
<OrderID>11036</OrderID>
<CustomerID>DRACD</CustomerID>
<EmployeeID>8</EmployeeID>
<OrderDate>1998-04-20T07:00:00</OrderDate>
<RequiredDate>1998-05-18T07:00:00</RequiredDate>
XML for ASP.NET Developers
346
<ShippedDate>1998-04-22T07:00:00</ShippedDate>
<ShipVia>3</ShipVia>
<Freight>149.47</Freight>
<ShipName>Drachenblut Delikatessen</ShipName>
<ShipAddress>Walserweg 21</ShipAddress>
<ShipCity>Aachen</ShipCity>
<ShipPostalCode>52066</ShipPostalCode>
<ShipCountry>Germany</ShipCountry>
</Orders>
Although you’ve seen how to create relationships between the Customers and Orders tables
through XML schemas, no examples have shown how to nest the orders under the appropriate
customer that placed them. The capability to represent hierarchical relationships can be very
useful, depending on the application. Listing 8.17 revisits the example shown in Listing 8.6 but
uses hierarchical relationships along with XSLT to output the different orders placed by a par-
ticular customer.
XML FEATURES
ADO.NET’S
LEVERAGING
48: var loc = document.all(id);
49: if (loc.style.display == ‘none’) {
50: loc.style.display = ‘block’;
51: } else {
52: loc.style.display = ‘none’;
53: }
54:
55: }
56: </script>
57: </head>
58: <body bgcolor=”#ffffff”>
59: <h2>
60: <b>Walking Hierarchical XML in DataSets with XSLT</b>
61: </h2>
62: <div id=”content” runat=”server” />
63: </body>
64: </html>
Creating hierarchical relationships is as simple as using the Nested property of the Relation
class. This property can be set to a Boolean value with false being the default. Using this
property is shown in line 21. By setting the property to true, all parent/child relationships will
be nested as appropriate when the DataSet is viewed as XML.
XML for ASP.NET Developers
348
In this example, all orders associated with a particular customer are automatically shown as
child elements in the XML document. This makes it easy to use XSLT to match each order and
write out the necessary data. Use of the Nested property is not restricted to just one relation-
ship. Each Relation object that is instantiated can have its Nested property set to true if nested
elements are desired. This means that orders can be placed under the appropriate customer that
placed the order, and the different products associated with each order can be placed under the
order.
This example also shows how a DataSet can be transformed into another structure such as
HTML by using the XmlDataDocument class and XSLT. Performing the transformation is as
simple as passing the DataSet into the XmlDataDocument’s constructor. The resulting object is
then passed into the XslTransform class’s Transform() method (line 41). This powerful fea-
ture allows you to transform data retrieved from virtually any data store into a specific struc-
ture via XSLT in your ASP.NET applications.
Summary
ADO.NET offers a very robust set of classes for working with data located in a variety of data
stores. Using the different ADO.NET managed providers, data found in an Oracle, Sybase,
Microsoft, or other databases can be accessed and even integrated into a single DataSet.
ADO.NET also allows much of the work involved with inserting, updating, or deleting to be
done while being completely disconnected from the data source. This is a big improvement
over “classic” ADO, in which disconnected RecordSets had to be explicitly created to be used
and were subject to COM marshaling.
Through using the DataSet and XmlDataDocument classes, data can be viewed in a relational
manner or as XML, and data contained within a DataSet can be moved to an
XmlDataDocument and queried using XPath statements. This opens up a whole new world of
opportunities for the creation of dynamic ASP.NET applications, especially if the applications
leverage XML. Many other features are associated with ADO.NET that can’t possibly be cov-
ered in this chapter alone. So many, in fact, that an entire book could be written on the topic.
Now that you’re familiar with how to access data using ADO.NET, the next chapter shows you
how to utilize SQL Server 2000’s native XML support to retrieve and update data. Combining
ASP.NET with SQL Server 2000 offers many exciting opportunities!
SQL Server 2000, XML, CHAPTER
9
and ASP.NET
IN THIS CHAPTER
• XML Features in SQL Server 2000 350
• Querying SQL Server 2000 Using HTTP 351
• Querying SQL Server 2000 Through HTTP
Using Templates, XPath, and XDR
Schemas 368
FIGURE 9.1
Creating a new SQL Server Virtual Directory.
The next screen prompts you for the virtual directory’s alias and physical location. Supply the
same information that you would for creating a normal IIS virtual. For the samples in this
XML for ASP.NET Developers
352
chapter, a virtual alias of Northwind will be used with a physical path pointing to a Northwind
directory located in the wwwwroot folder. Feel free to change the virtual’s alias and physical
path if desired.
After the preceding information has been completed, click the Security tab (located at the top
of the screen). Enter the correct user ID and password information to allow users to access
SQL Server 2000 through the virtual directory. This section allows you to specify a Windows
or SQL Server account type. It also allows you to use Windows Integrated Security, if desired.
Security is always a crucial piece of any application and the SQL Server 2000 documentation
provides detailed information about this important topic. Although database security is beyond
the scope of this book, properly securing a database is extremely important and should not be
taken lightly when building your ASP.NET applications.
After entering the correct security information, click the Data Source tab. The information
required by this section is similar to what you would provide when creating a DSN or
Universal Data Link (UDL). Simply supply the name of the SQL Server 2000 machine
along with the appropriate database within the server. For the examples in this chapter, the
Northwind database will be used as shown in Figure 9.2.
FIGURE 9.2
Establishing the proper SQL Server and Database to use with the Virtual Directory.
After completing the preceding steps, click the Settings tab. This tab contains several options
that can be selected to lock down the virtual directory’s access to the database. Figure 9.3
shows the different options.
SQL Server 2000, XML, and ASP.NET
353
CHAPTER 9
FIGURE 9.3
The Settings tab.
For the purposes of this chapter, check the boxes next to each option and click the Apply but-
ton. When building real applications, you’ll want to select the options that allow adequate
access to the SQL Server machine but also provide an adequate level of security. This topic as
well as a discussion of each option will be covered in the next few sections.
The Virtual Names tab allows templates, schemas, and dbobjects to be associated with the
newly created virtual directory. This screen allows you to tell the virtual directory where to go
when the calling application needs access to a particular template, schema, or dbobject. 9
Because these topics haven’t been covered to this point, I’ll defer a more in-depth discussion
the following URL into the location box. If you used a virtual alias other than Northwind,
you’ll need to substitute the appropriate name instead:
http://localhost/Northwind?sql=SELECT+*+FROM+Customers+WHERE+CustomerID=
‘ALFKI’+FOR+XML+AUTO&root=root
Before preceding much further, let’s break the preceding URL shown into pieces. To start, the
URL path to the virtual named Northwind is followed by the actual SQL query that needs to be
executed against the Northwind database. In this case, the query is defined as
SELECT * FROM Customers WHERE CustomerID=’ALFKI’
To allow the database to recognize the query properly, it is set equal to a QueryString parame-
ter named sql. You’ll also notice that the query is URL encoded so that it can be passed prop-
erly to the database. All spaces have been replaced with the + character and other characters
that invalidate the URL are URL encoded as well.
At the end of the query, two new keywords have been added. These are the FOR XML keywords
that serve the obvious purpose of returning data from the database in the form of XML. These
keywords are followed by the AUTO keyword that tells the database to return an XML document
that allows for elements to be nested. If this SQL query returned customers and orders, using
the AUTO keyword will automatically nest all order elements under the appropriate customer
that placed the order. All the fields specified in the query will be returned as attributes, as
shown in the previous XML document.
After the FOR XML AUTO keywords, another QueryString parameter is included named root.
This parameter is used to name the root element of the XML document that will be returned.
You can run the same query and substitute the value of northwind for the root QueryString
parameter value. The resulting XML document will look the same as the one shown earlier,
except that the root element will now be named northwind, as shown next:
<?xml version=”1.0” encoding=”utf-8” ?>
<northwind>
<Customers CustomerID=”ALFKI” CompanyName=”New Name” ContactName=”Maria
Anders” ContactTitle=”Sales Representative” Address=”Obere Str. 57”
SQL Server 2000, XML, and ASP.NET
355
CHAPTER 9
Although this sample is quite simple, more complex queries that join different tables can be
executed using HTTP as well. The following query builds on the previous one by joining the
Customers and Orders tables found in the Northwind database:
http://localhost/northwind?sql=SELECT+Customer.CustomerID%2c
Customer.ContactName%2c%5bOrder%5d.OrderID+FROM+Customers+Customer+
INNER+JOIN+Orders+%5bOrder%5d+ON+Customer.CustomerID%3d%5bOrder%5d.CustomerID+
FOR+XML+AUTO&root=Northwind
SQL SERVER
ASP.NET
In cases where you do not want any nesting to occur between the customers and orders, SQL
Server 2000 offers another keyword that can used instead of AUTO. This alternative keyword is
named RAW. Using the RAW keyword results in all fields being grouped into one XML element
“row” structure, as shown next:
<Northwind>
<row CustomerID=”ALFKI” ContactName=”Maria Anders” OrderID=”10643”/>
<row CustomerID=”ALFKI” ContactName=”Maria Anders” OrderID=”10692”/>
<!--.....More Rows Would Follow...-->
</Northwind>
You’ll see in the next few examples that the FOR XML keywords can be followed by several other
keywords in addition to AUTO and RAW that allow for complete customization of XML data.
XML for ASP.NET Developers
356
You’ll notice that as HTTP queries become more complex, URL encoding them can be diffi-
cult to do by hand. Listing 9.1 shows how to make this process easier by using the ASP.NET
Server object’s UrlEncode() method. It allows a regular SQL query to be entered and outputs
either a URL-encoded string version of the query or executes the query and returns the result-
ing XML document. Using the same type of logic shown in this listing, you can automatically
URL encode SQL queries in your ASP.NET pages.
Virtually any query can be executed using HTTP. For example, assume a particular application
has a stored procedure named sp_GetXml, as shown next:
CREATE PROCEDURE sp_GetXml
(
@CustomerID varchar(5)
)
AS
BEGIN
SELECT CustomerID, CompanyName, ContactName 9
FROM Customers
SQL SERVER
ASP.NET
FOR XML AUTO
END
To execute this procedure and pass in the appropriate argument, the following HTTP query can
be used:
http://localhost/northwind?sql=exec+sp_GetXml+’A’&root=root
This same logic applies to more advanced stored procedures and allows for dynamic substitu-
tion of the argument values (“A” in this case), depending on the results that the end user wants
to see.
At this point you may be wondering why you would ever use an HTTP query to obtain XML
data from a database located on SQL Server 2000? Because it returns XML data directly to the
XML for ASP.NET Developers
358
browser, what good is it? Thinking back to Chapter 7, “Transforming XML with XSLT and
ASP.NET,” you’ll remember that the XPathDocument class has several constructors that can be
used to load an XML document. One of those constructors accepts a string specifying the path
to the XML document. This presents an excellent opportunity to obtain an XML document
directly from a SQL Server database.
The next listing shows how data from SQL Server 2000 can be loaded directly into an
XPathDocument class and then transformed into HTML using the XslTransform class. Listing
9.2 shows this process.
Listing 9.2 Loading XML Data Directly from SQL Server 2000 into the XPathDocument Class
<%@ Import Namespace=”System.Xml.XPath” %>
<%@ Import Namespace=”System.Xml.Xsl” %>
<script language=”C#” runat=”Server”>
void Page_Load(object sender, EventArgs e) {
string sql = Server.UrlEncode(@”SELECT Customers.CustomerID,
Customers.ContactName, Orders.OrderID,
Orders.CustomerID FROM Customers
INNER JOIN Orders
ON Customers.CustomerID = Orders.CustomerID
FOR XML AUTO”);
string url = “http://localhost/northwind?sql=” + sql +
➥“&root=Northwind”;
try {
XPathDocument xmlDoc = new XPathDocument(url);
XslTransform xslDoc = new XslTransform();
xslDoc.Load(Server.MapPath(“customers.xsl”));
xslDoc.Transform(xmlDoc,null,Response.Output);
}
catch (Exception exc) {
Response.Write(exc.ToString());
}
}
</script>
Listing 9.3 follows along the lines of the previous listing but shows how XML data from SQL
Server 2000 can be loaded directly into a DataSet class using its ReadXml() method.
LISTING 9.3 Loading XML Data Directly from SQL Server 2000 into the DataSet Class
1: <%@ Import Namespace=”System.Data”%>
2: <%@ Import Namespace=”System.Xml”%>
3: <script language=”C#” runat=”server”>
SQL Server 2000, XML, and ASP.NET
359
CHAPTER 9
SQL SERVER
35: Customers.DataBind();
ASP.NET
36:
37: view.Table = ds.Tables[1];
38: Orders.DataSource = view;
39: Orders.DataBind();
40: //Let’s look at the schema
41: schemaDiv.InnerHtml = ds.GetXmlSchema();
42:
43: }
44: </script>
45: <html>
46: <body bgcolor=”#ffffff”>
47: <form runat=”server”>
XML for ASP.NET Developers
360
Lines 5–11 start things off by URL encoding a SQL statement that will be executed using an
HTTP query. After this string is created, it is used to hit the database and generate XML data
that is then loaded into the DataSet (lines 17 and 18). You’ll notice that an XSD schema is also
loaded into the DataSet so that the data read from the database is structured correctly (line 16).
Lines 21–30 take care of adding two new rows. One row is added to the Customers XML
information (table[0]) and another row is added to the Orders XML information (table[1]).
After this operation is complete, two DataViews are created based on the two tables and then
bound to an ASP.NET DataGrid. Figure 9.4 shows what the code generates in the browser.
SQL Server 2000, XML, and ASP.NET
361
CHAPTER 9
FIGURE 9.4
HTML generated by Listing 9.3.
SQL SERVER
<Customers CustomerID=”DLWID” ContactName=”Dan Wahlin” />
ASP.NET
<Orders OrderID=”11000” CustomerID=”DLWID” />
</NewDataSet>
The first thing that stands out is that the newly added Order row isn’t placed into the XML
document as a child of the Customers element that it relates to (CustomerID=”DLWID”), as is
the case with the other Orders elements. This is because the new Orders row was added to the
Orders table (table[1]) and therefore has no relationship with the newly added Customers
row. Although a relationship between the CustomerID attribute in the Customers element and
the CustomerID attribute in the Orders element can be created using the DataRelation class
along with the DataSet’s Relations property, there is another way to accomplish this task
with the DataSet class.
Looking back at Figure 9.4, you’ll see an interesting feature of the DataSet. Each bound table
shows a Customers_id field that was not a part of the original SQL statement executed using
XML for ASP.NET Developers
362
the HTTP query. Where did this mysterious field come from? Because the XML information
for Orders is subordinate to the Customers XML information, the DataSet automatically
added an identity field to help track which Orders are associated with which Customers.
Looking at the second DataGrid on the page, you can see that several of the orders have the
same Customers_id number. These different Orders elements show up under the same
Customers element. However, the newly added Orders row doesn’t have any value in its
Customers_id field. This is because the DataSet doesn’t know that it is related to a specific
Customers element.
To put the newly created Order row under the appropriate Customer element in the DataSet,
the following line of code can be added after line 30 in Listing 9.3:
ds.Tables[1].Rows[ds.Tables[1].Rows.Count-1][“Customers_id”] =
ds.Tables[0].Rows[ds.Tables[0].Rows.Count-1][“Customers_id”];
This line of code makes the new Orders row a child element of the proper Customers element
by relating their Customers_id fields. Adding this one line of code results in the following
XML fragment:
<Customers CustomerID=”DLWID” ContactName=”Dan Wahlin”>
<Orders OrderID=”11000” CustomerID=”DLWID”/>
</Customers>
You can see that the newly added Order is now a child element of the new Customer element
because the DataSet now knows that the two elements are related. A more correct way to
relate the newly added order to the customer is to associate the rows using the SetParentRow()
method:
ds.Tables[1].Rows[ds.Tables[1].Rows.Count-1].SetParentRow(
ds.Tables[0].Rows[ds.Tables[0].Rows.Count-1])
Keep in mind that if nesting and relationships between tables wasn’t specified in advance, the
SetParentRow() method would not do anything useful. Fortunately, the nesting was specified
in the XML schema and in the structure of the XML data returned from SQL Server 2000 that
was initially loaded into the DataSet. Because of this, the SetParentRow() method can be
called when nesting among newly added rows needs to occur.
Use of the ELEMENTS keyword is restricted to cases where the FOR XML AUTO keywords are also
used. Whenever data needs to be contained within elements rather than attributes, simply add
FOR XML AUTO, ELEMENTS to the end of the SQL SELECT statement or stored procedure.
The FOR XML AUTO keywords can also be followed by the XMLDATA and BINARY BASE64 key-
words. The XMLDATA keyword instructs the database to return an inline XDR schema along
with the XML data. The BINARY BASE64 keywords are used to BASE64 encode binary
columns that would otherwise contain illegal characters. More details about working with
binary data can be found in the SQL Server 2000 documentation.
FOR+XML+AUTO</sql:query></ROOT>
Querying the database in this manner still poses a potential security risk because an end user
with knowledge of templates could use them on the query string to manipulate data in the data-
base. And it’s obviously not the easiest way to write out a query.
To combat the potential problem with security, SQL Server virtual directories (discussed ear-
lier) can be configured to block HTTP URL queries. Instead, queries can be directed toward
XML template files that contain the desired SQL query. Before discussing template specifics,
let’s revisit the SQL Server virtual directory administration console discussed earlier. As a
refresher, you can reach this tool by going to the Start, Programs, Microsoft SQL Server pro-
gram group and selecting the Configure SQL XML Support in IIS option. Open up the com-
puter hierarchy to reveal the default Web site. Click this, and in the right pane you’ll see the
Northwind virtual directory created earlier in the chapter. Right-click this virtual, choose
Properties, and then go to the Settings tab.
The first check box, Allow URL Queries, should be checked. To prevent users from accessing
the database through a URL query, uncheck the box. This will only allow SQL HTTP queries
to be executed against the database that are XML templates, XPath queries, or form template
posts. HTTP URL queries will be blocked completely.
To allow XML templates to perform SQL queries, go to the Virtual Names tab and click the
New button. Although the virtual name can be anything you like, the examples that follow will
use a name of Templates. Enter this name, and in the drop-down box named Type, select
Template. Next, either type in a path to where your XML templates will be stored or click the
Browse button. A path of d:\inetpub\wwwroot\testbed\chapter9 was used for the examples
that follow, although the path can point to anywhere you would like. After you have supplied
all the necessary information, click the Save button.
Now that a virtual has been mapped to a folder designated to hold XML query templates, let’s
take a look at how to create a valid template that can be used to execute SQL queries. Listing
9.4 shows a sample template. It uses a namespace prefix of sql with a URI of urn:schemas-
microsoft-com:xml-sql. One of the elements is named query. It serves the rather obvious
purpose of marking up SQL query statements located within the template file.
After the template is saved to the proper directory, you can test it by going to the following URL:
http://localhost/northwind/templates/listing9.4.xml
Breaking the URL down into individual pieces, you can see that the Northwind virtual root is
first specified (case does not matter here). Within this virtual, the templates virtual name is
then used. It maps to the physical templates directory that was configured earlier. Finally, the
name of the template is specified (listing9.4.xml). Executing this template using a browser
results in an XML document containing different Orders elements nested under Customers
elements, as shown earlier in the chapter.
Several advantages exist to using templates as compared to URL queries. First of all, an end
user now has no control over changing the SQL statements. By removing access to SQL Server
through URL queries, only an individual with write access to the template file can make
changes. This prevents inserts, updates, or deletes from being performed in an unauthorized
manner. Second, XML templates support the dynamic inclusion of parameters. This allows you
to change the value of a SQL WHERE clause without having to make adjustments to the actual
template file.
Working with parameters is as simple as including an XML header element, as shown in
Listing 9.5: 9
Within the header element, a param element has been defined with a name attribute equal to
CustomerID. This parameter is given a default value of A. You can use the parameter in the
template just like you would in a stored procedure. Simply append the “@” character to the
front of the parameter and then place it in within the SQL statement or stored procedure. For
this example, the CustomerID parameter is used in the WHERE clause. If the parameter is set to
“B”, the SQL statement will return all rows from the Customers and Orders tables with a
CustomerID that starts with “B.”
Calling this template and passing the correct CustomerID parameter value is as simple as
adding the parameter name and value on the end of the query string, as shown next:
http://localhost/northwind/templates/listing9.5.xml?CustomerID=B
NOTE
Parameters used in XML templates are case sensitive just as XML is. If customerID was
used as the parameter name on the query string instead of CustomerID, the default
value of “A” would be used no matter what value was passed in.
Templates can also be used to execute stored procedures. Listing 9.6 shows an example of call-
ing the sp_GetXml procedure shown earlier in the chapter:
SQL SERVER
32: “templates/getCustomerID.xml”;
ASP.NET
33: DataSet ds = new DataSet();
34: ds.ReadXml(url);
35: customers.DataTextField = “CustomerID”;
36: customers.DataSource = ds.Tables[0].DefaultView;
37: customers.DataBind();
38: }
39: </script>
40: <html>
41: <body bgcolor=”#ffffff”>
42: <form runat=”server” ID=”Form1”>
43: <b>Select a CustomerID to View:</b>
44: <asp:DropDownList id=”customers” runat=”server” />
XML for ASP.NET Developers
368
This simple example doesn’t introduce anything new in the way of classes or methods. In fact,
everything just shown was covered in Chapters 6–8. However, it does show different ways you
can interact with data in SQL Server 2000 using ASP.NET.
• The following string functions are not currently supported: string(), concat(),
starts-with(), contains(), substring-before(), substring-after(), substring(),
string-length(), normalize(), translate().
Keep in mind that as different service packs and “Web Releases” are released, this information
may change. To stay up to date with the different SQL Server 2000 XML features, visit
http://msdn.microsoft.com/sqlserver.
SQL SERVER
Used on elements that do not map to a specific table or field.
ASP.NET
sql:is-constant
The annotation value can be a Boolean. Uses of this annota-
tion include specifying top-level nodes in an XML document
or container nodes. XML items with this annotation will show
in the output.
sql:key-fields Specifies that the element or attribute is the primary key that
uniquely identifies a row in a table.
sql:id-prefix Used to create a valid XML ID, IDREF, or IDREFS attribute
type. Because these types must start with an alphabetic char-
acter or underscore character, a valid prefix is specified that
will be used to start the ID, IDREF, or IDREFS type attribute.
XML for ASP.NET Developers
370
Without the schema and associated SQL Server annotations, it would be very difficult to match
up the tables and fields within a database to different elements and attributes within an XML
document. How could an XPath statement possibly return results from the database if it had no
idea what fields to look for when an attribute or element is used in an XPath query? The obvi-
ous answer is that XPath queries cannot access the database on their own. They must be used
with schemas to identify the structure of the data within the database.
The best way to learn about using the annotations within a schema is to see an example of
them in action. Listing 9.8 contains a modified version of the XDR schema used in the SQL
Server 2000 documentation. It can be used to query the Northwind database using XPath.
SQL SERVER
45: <attribute type=”OrderDate” />
ASP.NET
46: <attribute type=”RequiredDate” />
47: <attribute type=”ShippedDate” />
48:
50: <element type=”OrderDetail”>
51: <sql:relationship
52: key-relation=”Orders”
53: key=”OrderID”
54: foreign-relation=”[Order Details]”
55: foreign-key=”OrderID” />
56: </element>
57: <element type=”Employee”>
58: <sql:relationship
XML for ASP.NET Developers
372
The first thing you’ll notice when looking at Listing 9.8 is that it follows the XDR specifica-
tion by declaring element and attribute types. These declarations are used to define the struc-
ture of data within the database. The schema annotations are integrated directly into the
schema as attributes (except in the case of the relationship element, which is discussed
later). Each annotation is associated with the sql namespace prefix. The URI for this
SQL Server 2000, XML, and ASP.NET
373
CHAPTER 9
Line 12 contains an attribute declaration named Orders that has an IDREFS data type. To
ensure that the data returned from the database follows the IDREFS naming restrictions, the
sql:id-prefix annotation is used. This dynamically adds the value “Ord-” to each referenced
IDREF value.
Lines 20–24 contain the only element annotation that SQL Server 2000 uses. This annotation,
named relationship, is used to specify which IDREF values should be added as values for
the Orders attribute. It automatically associates each order a customer places with the appro-
priate OrderID by using the key-relation, key, foreign-relation, and foreign-key attrib-
utes. The key-relation attribute specifies the primary relation table; Customers, in this
example. The key attribute then locates the primary key within this table (CustomerID). These
two attributes are followed by the foreign-relation attribute, which references the foreign
relation table (Orders). The foreign-key attribute then identifies the primary key within this
table (CustomerID). The end result of using these various annotations on the Orders attribute is
a list of IDREF values each prefixed with “Ord-.”
Jumping to line 58 in Listing 9.8, another annotation named key-fields is used to identify 9
what UnitPrice and Quantity fields should be pulled from the Order Details table. The
SQL SERVER
ASP.NET
identify a given row in the table.
Although Listing 9.8 does not show how to use each annotation listed in Table 9.1, it gives you
a good feel for how annotations can be used to map database table fields to XML items and
create relationships among different tables. For more information on the annotations that were
not covered, refer to the SQL Server 2000 documentation.
After a schema is created and annotations are added, it can be saved in two ways. First, it can
be saved in the same folder as the XML template files were saved. Doing this allows templates
to reference the schema based on its relative path. This process will be discussed in a moment.
Second, the schema can be saved in a folder specially designed to hold schemas. Referring
back to our discussion on setting up the Northwind SQL Server virtual directory earlier in the
XML for ASP.NET Developers
374
chapter, you’ll remember that when XML templates needed to be used, a special folder was
configured for this purpose. This was done by going to the Virtual Settings tab of the virtual
directory’s properties sheet. Following the same steps outlined earlier, a schema folder can be
created by clicking the Add button on the Virtual Settings tab, giving the folder to hold
schemas a virtual name (“Schemas” is always a good name), selecting schema from the drop-
down box, and then specifying the physical path to the schemas folder. Note that a schema vir-
tual name can also point to a particular schema file. Pointing to a file can hide the fact that the
returned data is actually coming from a database.
Schemas present an excellent opportunity to define custom XML structures that can then be
returned from the database using XPath queries. Although an alternative to creating complex
XML document structures exists in the form of EXPLICIT mode queries (discussed later),
schemas present a straightforward way of creating XML structures that fit a specific mold used
by ASP.NET applications. To see how this works, let’s take a look at how XPath queries can
use schemas to return XML documents.
This XPath query results in the following XML document being returned:
<?xml version=”1.0” encoding=”utf-8” ?>
<Northwind>
<Order OrderID=”Ord-10643” EmployeeID=”6” OrderDate=”1997-08-25T00:00:00”
RequiredDate=”1997-09-22T00:00:00” ShippedDate=”1997-09-02T00:00:00”>
<Employee EmployeeID=”Emp-6” LastName=”Suyama” FirstName=”Michael”
Title=”Sales Representative”/>
<OrderDetail ProductID=”Prod-28” UnitPrice=”45.6” Quantity=”15”>
<Discount>0.25</Discount>
</OrderDetail>
<OrderDetail ProductID=”Prod-39” UnitPrice=”18” Quantity=”21”>
<Discount>0.25</Discount>
</OrderDetail>
<OrderDetail ProductID=”Prod-46” UnitPrice=”12” Quantity=”2”>
<Discount>0.25</Discount>
</OrderDetail>
</Order>
<!-- .....MORE ORDERS FOLLOW...-->
</Northwind>
SQL Server 2000, XML, and ASP.NET
375
CHAPTER 9
The first thing you’ll notice is that the XPath query immediately follows the schema filename
in the URL (listing9.8.xdr). The XPath query finds all Order elements that have a parent
element named Customer with a CustomerID attribute equal to ALFKI. It should be noted that
the query is case sensitive. Typing /customer[@customerID=’ALFKI’]/Order will result in an
error being returned because the case of the element and attribute being named does not match
the case specified in the schema. It’s also important to note that a root element name must be
supplied after the XPath query, as shown, for a well-formed XML document to be returned.
Turning off URL querying capability (through the virtual directory’s Settings tab) will not
affect XPath queries, because they are treated separately. In cases where querying the database
through the use of XPath queries is undesirable, an administrator can disable them by uncheck-
ing the Allow XPath option on the Settings tab of the appropriate virtual directory’s property
sheet. Any attempts to use them after they have been disabled will result in the following error
condition:
ERROR: 400.100 Bad Request
HResult: 0x80004005
Source: Microsoft SQL isapi extension
Description: XPath queries are not allowed.
Although useful, querying via XPath queries embedded in a URL can be problematic because
of URL-encoding issues. The next section demonstrates how to embed XPath queries directly
into XML template files.
SQL SERVER
LISTING 9.9 Embedding an XPath Query into an XML Template ASP.NET
1: <Northwind xmlns:sql=”urn:schemas-microsoft-com:xml-sql”>
2: <sql:xpath-query mapping-schema=”listing9.8.xdr”>
3: /Customer[@CustomerID=’ALFKI’]/Order
4: </sql:xpath-query>
5: </Northwind>
Like the SQL queries embedded in XML template files earlier in the chapter, XPath queries
use the urn:schemas-microsoft-com:xml-sql URI along with the sql prefix to identify
custom elements and attributes used in the template. For XPath queries, an element named
xpath-query is used to mark up the query syntax. This element also has an attribute named
XML for ASP.NET Developers
376
mapping-schema that is used to identify the path to the appropriate schema file
(listing9.8.xdr in this case) used to map tables and fields to specific XML items.
Listing 9.10 shows another template file that uses a more complex XPath query.
When executed, the XPath query returns which employee was involved with handling a spe-
cific order placed by a customer, as shown next:
<Northwind xmlns:sql=”urn:schemas-microsoft-com:xml-sql”>
<Employee EmployeeID=”Emp-6” LastName=”Suyama” FirstName=”Michael”
Title=”Sales Representative”/>
</Northwind>
XPath queries used within template files can also accept parameters. This process functions in
a manner similar to parameters used in XSLT style sheets. Like XSLT, the $ character is used
to designate a variable. Listing 9.11 shows how to incorporate variables into templates contain-
ing XPath queries.
Passing a parameter to the template can be accomplished by passing the parameter name along
with its associated value in the URL as shown next:
http://localhost/northwind/templates/listing9.11.xml?ID=ORD-10643
As was shown earlier in the chapter, passing the preceding URL to the XPathDocument class
constructor (or the Load() method of an XmlDocument class) or to the ReadXml() method of the
DataSet class results in ASP.NET pages that access XML data directly. For an example of
doing this, please refer back to Listing 9.7.
SQL Server 2000, XML, and ASP.NET
377
CHAPTER 9
Northwind
Parent = Null, Tag = 1
Customer
9
SQL SERVER
ASP.NET
Order
Parent = 2, Tag = 3
OrderDetails
Parent = 3, Tag = 4
FIGURE 9.5
Understanding EXPLICIT mode query hierarchies.
As you can see, EXPLICIT mode queries assign a tag number and a parent value to every XML
element within a document. This allows relationships between elements to be established and
XML for ASP.NET Developers
378
XML structures to be created based on these relationships. For example, the OrderDetails ele-
ment in Figure 9.5 has a tag number of 4 because it’s the fourth tag listed in the document.
When doing a query against the database, this element can be nested as a child of the Order
element by identifying that its parent tag number is 3. Had the application required the
OrderDetails tag to be a child element of the Customer element, a parent tag number of 2
could have been identified.
Now that you have a feel for how EXPLICIT mode queries use tag numbers to create hierarchi-
cal XML document structures, Listing 9.12 demonstrates a query that returns the following
XML document:
<MyCustomer CustID=”ALFKI”>
<MyCustomerOrder OrdID =”10643”/>
<MyCustomerOrder OrdID =”10692”/>
<MyCustomerOrder OrdID =”10702”/>
<MyCustomerOrder OrdID =”10835”/>
<MyCustomerOrder OrdID =”10952”/>
<MyCustomerOrder OrdID =”11011”/>
</MyCustomer >
1: SELECT 1 as Tag,
2: NULL as Parent,
3: Customers.CustomerID as [MyCustomer!1!CustID],
4: NULL as [MyCustomerOrder!2!OrdID]
5: FROM Customers
6: WHERE Customers.CustomerID=’ALFKI’
7:
8: UNION ALL
9:
10: SELECT 2,
11: 1,
12: Customers.CustomerID,
13: Orders.OrderID
14: FROM Customers, Orders
15: WHERE Customers.CustomerID = Orders.CustomerID
16: AND Customers.CustomerID = ‘ALFKI’
17: ORDER BY [MyCustomer!1!CustID], [MyCustomerOrder!2!OrdID]
18: FOR XML EXPLICIT
At first glance, this SQL query may look rather strange. After all, it uses ! characters and
strangely named field aliases. However, when you break the query down into small pieces, the
purpose and reason for the oddities become more clear and understandable.
SQL Server 2000, XML, and ASP.NET
379
CHAPTER 9
Let’s first examine lines 1 through 6, which are used to build the MyCustomer element. If
you’ve worked with SQL much, you’ll recognize this as a fairly simple SELECT query, except
that it has a few odd characters mixed in it. This first SELECT query serves the purpose of
defining all elements and attributes that will be returned in the XML document. Lines 1 and 2
create two metadata fields named Tag and Parent. These fields are used to track node position
and hierarchical relationships, as shown earlier in Figure 9.5. Because the MyCustomer element
appears first in the XML document, it has a parent value of null. This is because it is the root
of the tree and therefore has no parent. The tag number for the MyCustomer element is given a
value of 1 because it is the first element tag in the XML document. Line 3 then selects the
CustomerID field from the Customers table and assigns it a special alias that contains three
pieces of information, all separated by the ! character. This alias is based on a special format
recognized by SQL Server 2000:
ElementName!TagNumber!AttributeName!Directive
The first part of the alias is the name of the element that the field will be associated with. In
this case, it gets associated with the MyCustomer element. The second piece of information is
the tag number of the element it will be associated with, which is 1. The third piece of infor-
mation is the name of the attribute that will show in the XML document. In this example,
CustID is specified as the attribute name. It’s important to realize that all field aliases are
designed to be attributes by default. To change them to elements, the element directive must
be specified in the Directive section of the alias. We’ll discuss directives a little later in the
chapter.
Line 4 goes through the same process as outlined previously. It selects NULL and gives it a spe-
cial alias. First it assigns the field to the MyCustomerOrders element, which has a tag value of
2 because it is the second element tag in the XML document. Next, it gives the attribute a
name of OrdID. Why is this field being created in the resultset when it is assigned a NULL 9
value? A little earlier it was mentioned that this first SELECT statement contains all the elements
SQL SERVER
ASP.NET
will be returned, a place for it must be reserved initially for a union operation to occur (dis-
cussed later). This is accomplished by defining that there will be an XML attribute named
OrdID but that the attribute doesn’t have a value yet (it is NULL). This will become more clear
as you look at the second SELECT statement in Listing 9.12. After the four fields are defined,
the final part of the SELECT statement is shown in line 6. This SQL code simply ensures that
only the customer associated with the unique CustomerID field will be returned.
Line 8 tells the query to perform a Union operation by using the UNION ALL SQL keywords.
Union operators serve the purpose of combining all fields from two or more SQL SELECT state-
ments into one. For this operation to work correctly, each of the SELECT statements involved in
the union must contain the same number of fields. These fields must also be named the same
XML for ASP.NET Developers
380
and must appear in the same order in every SELECT statement. To better understand how a
union operation works, Figure 9.6 provides a graphical representation.
SELECT 1 SELECT 2
ID Name ID Name
UNION
ID Name
123 NULL
234 Dan
FIGURE 9.6.
Graphical representation of a Union operation involving two SELECT statements.
As shown, the Union operation simply combines the two SELECT statements. This is possible
because the fields are named the same and appear in the same order. Getting back to Listing
9.12, the UNION ALL statement combines the first SELECT statement with the one that follows.
The first SELECT statement begins the process of building the MyCustomers element (tag 1).
Because of the aliases assigned to each field in this first statement, you can see that the CustID
attribute is the only one that should be associated with the MyCustomers element. Although the
OrdID attribute is also listed, remember that it isn’t associated with tag 1. Its association is with
tag 2 as shown in its alias.
The second SELECT statement begins the process of building the MyCustomerOrders element
(tag 2). This element contains the OrdID attribute. We know from the first SELECT statement
that OrdID is associated with tag 2. Although the CustomerID field is listed in this second
select statement (line 12), it is not included as an attribute of the MyCustomerOrders element
because its alias points to tag 1 (the MyCustomer element). It is listed to allow the union opera-
tion to combine the two SELECT statements as mentioned earlier. Any value can actually be put
in for the CustomerID value in this second statement as long as the value can be converted to
the proper data type defined for the CustomerID field (nvarchar in this case).
Line 17 finishes off the SQL statement by determining how to order the results, and line 18
uses the magical FOR XML EXPLICIT keywords. These keywords are necessary for XML to be
SQL Server 2000, XML, and ASP.NET
381
CHAPTER 9
returned from the database in the structure specified by the two SELECT statements. By using
the EXPLICIT keyword, you are telling the database to evaluate the values of the Tag and
Parent metadata fields to organize the resulting XML document’s structure.
Table 9.2 shows all the fields and their respective values after the two SELECT statements have
been executed and combined using the UNION All syntax.
SQL SERVER
ASP.NET
ID Used to represent an attribute ID type. This can be used with IDREF and
IDREFS to build relationships. This directive is useful only when FOR XML
EXPLICIT, XMLDATA is specified in the query.
IDREF Used to represent an attribute IDREF type. This can be used with IDREF and
IDREFS to build relationships. This directive is useful only when FOR XML
EXPLICIT, XMLDATA is specified in the query.
IDREFS Used to represent an attribute IDREFS type. This can be used with IDREF and
IDREFS to build relationships. This directive is useful only when FOR XML
EXPLICIT, XMLDATA is specified in the query.
hide This attribute is not displayed when the hide directive is specified. This is
useful when you need to order the results by an attribute that you do not
want to appear in the XML document.
XML for ASP.NET Developers
382
The directives listed previously can be used to gain more control over what type of content is
returned by SQL Server 2000. Listing 9.13 builds on the previous query by using ID, IDREF,
element, and cdata directives to build a more detailed XML document structure. The entire
SQL statement is embedded within an XML template file.
The concepts used to build this query are the same as those discussed in Listing 9.12. 9
However, this query uses directives in lines 10 and 11 to specify that the attributes are ID and
ASP.NET
the FOR XML EXPLICIT keywords are followed by another keyword named XMLDATA. This tells
SQL Server to place an inline schema in the resulting XML document. Line 12 uses the ele-
ment directive to place the result of (UnitPrice-Discount)*Quantity into an element named
total. This query also adds one more hierarchical level of tags by going out to tag number 3.
Executing the query in Listing 9.13 results in the following XML document (only part of the
document is shown). It’s interesting to note that the inline schema doesn’t fully define the
structure of the XML document. Looking at the schema contents, you can see that the
ElementType named Customer does not include the Order and OrderDetails ElementTypes.
It’s clear that the schema is based on the three distinct SELECT statements included in
EXPLICIT mode query rather than the XML that is generated.
XML for ASP.NET Developers
384
<Northwind xmlns:sql=”urn:schemas-microsoft-com:xml-sql”>
<Schema name=”Schema1” xmlns=”urn:schemas-microsoft-com:xml-data”
xmlns:dt=”urn:schemas-microsoft-com:datatypes”>
<ElementType name=”Customer” content=”mixed” model=”open”>
<AttributeType name=”cid” dt:type=”string”/>
<AttributeType name=”name” dt:type=”string”/>
<attribute type=”cid”/>
<attribute type=”name”/>
</ElementType>
<ElementType name=”Order” content=”mixed” model=”open”>
<AttributeType name=”id” dt:type=”i4”/>
<AttributeType name=”date” dt:type=”dateTime”/>
<AttributeType name=”RepID” dt:type=”i4”/>
<attribute type=”id”/>
<attribute type=”date”/>
<attribute type=”RepID”/>
</ElementType>
<ElementType name=”OrderDetails” content=”mixed” model=”open”>
<AttributeType name=”id” dt:type=”id”/>
<AttributeType name=”pid” dt:type=”idref”/>
<attribute type=”id”/>
<attribute type=”pid”/>
<element type=”total”/>
</ElementType>
<ElementType name=”total” content=”textOnly” model=”closed”
dt:type=”string”/>
</Schema>
<Customer xmlns=”x-schema:#Schema1” cid=”ALFKI” name=”Maria Anders”>
<Order id=”10643” date=”1997-08-25T00:00:00” RepID=”6”>
<OrderDetails id=”10643” pid=”28”>
<total>$680.25</total>
</OrderDetails>
<OrderDetails id=”10643” pid=”39”>
<total>$372.75</total>
</OrderDetails>
<OrderDetails id=”10643” pid=”46”>
<total>$23.5</total>
</OrderDetails>
</Order>
<!-- ... MORE ORDER WOULD FOLLOW...-->
</Customer>
</Northwind>
You can obtain these XML results by navigating to the following URL:
http://localhost/northwind/templates/listing9.13.xml
SQL Server 2000, XML, and ASP.NET
385
CHAPTER 9
Listing 9.14 shows an example of using the OPENXML rowset functionality to select values from
an existing XML document. As outlined previously, the document is loaded into memory and
parsed. On completing the parsing operation, data is extracted and formatted into a rowset.
Finally, the memory allocated to hold the XML document is released.
SQL SERVER
ASP.NET
4: <ROOT>
5: <Customer CustomerID=”DLWID” ContactName=”Dan Wahlin”>
6: <Order OrderID=”10248” CustomerID=”DLWID”
7: OrderDate=”2000-09-09T00:00:00”>
8: <OrderDetail ProductID=”11” Quantity=”12”/>
9: <OrderDetail ProductID=”42” Quantity=”10”/>
10: </Order>
11: </Customer>
12: <Customer CustomerID=”JJDID” ContactName=”John Doe”>
13: <Order OrderID=”10283” CustomerID=”JJDID”
14: OrderDate=”2000-09-04T00:00:00”>
15: <OrderDetail ProductID=”72” Quantity=”3”/>
16: </Order>
XML for ASP.NET Developers
386
This query can be executed by placing it into SQL Query Analyzer or a similar tool. Doing so
returns the following results:
Let’s break Listing 9.14 into pieces to better understand how OPENXML works. After defining
variables in lines 1 and 2, the XML document that will be parsed is loaded into the XmlDoc
variable. Line 19 makes a call to the sp_xml_preparedocument stored procedure, which loads
the XML document into memory and parses it. Next, a SELECT statement is created that uses
OPENXML to query the XML data. The handle to the XML document (@docID) is passed in along
with an XPath statement that identifies the nodes that will be converted to a rowset. Finally, a
flag is passed in to specify that the data for the fields in the rowset will be found in attributes
within the XML document. Table 9.4 shows the possible flag values.
Lines 22–25 create the schema declaration that is used to map attributes located within
@XmlDoc to fields that will be found in the rowset. This SQL code dictates that four fields will
be contained within the rowset. This is accomplished by listing the field name, data type, and
location within the XML document (an XPath statement). After these fields are configured
and the rowset is generated, the handle to @XmlDoc (named @docID) is passed to the
sp_xml_removedocument stored procedure to remove the XML document from memory.
OPENXML is most useful when you need to insert data located within an XML document into
relational tables. Listing 9.15 builds on the previous listing by allowing multiple inserts to
occur.
LISTING 9.15 SQL Code for Inserting XML Data into Tables Using OPENXML
1: <Northwind xmlns:sql=”urn:schemas-microsoft-com:xml-sql”>
2: <status>
3: <sql:header>
4: <sql:param name=”XmlDoc”/>
5: </sql:header>
6: <sql:query>
7: DECLARE @docID int
8: EXEC sp_xml_preparedocument @docID OUTPUT, @XmlDoc
9: INSERT INTO Customers
10: SELECT *
11: FROM OPENXML(@docID, ‘//Customers’)
12: WITH Customers
13: IF @@ERROR > 0
14: SELECT ‘Error’
15: ELSE
16: SELECT ‘Success’
17: EXEC sp_xml_removedocument @docID 9
18: </sql:query>
SQL SERVER
ASP.NET
20: </Northwind>
The SQL code shown in Listing 9.15 performs the simple function of inserting the CustomerID,
ContactName, and Address information found in the XML document into the Customers table
in the Northwind database. This process is kicked off by an ASP.NET page that presents the
user with a form containing three fields. Upon submitting the form, the ASP.NET page
processes the values entered in the form fields and creates an XML document. This document is
then passed as a parameter to the template shown in Listing 9.15. The XML result returned
from the template is loaded into an XmlDocument class and then written out to the page. Listing
9.16 shows the ASP.NET page.
XML for ASP.NET Developers
388
LISTING 9.16 ASP.NET Page Used to Send Information to the XML Template
1: <%@ Import Namespace=”System.Xml” %>
2: <script language=”C#” runat=”server”>
3: void SubmitButton_Click(Object sender, EventArgs e) {
4: Response.ContentType = “text/xml”;
5: string xmlDoc = “<Northwind><Customers CustomerID=\”” +
6: CustomerID.Text;
7: xmlDoc += “\” ContactName=\”” + ContactName.Text;
8: xmlDoc += “\” CompanyName=\”” + CompanyName.Text +
9: “\”/></Northwind>”;
10: string url = “http://localhost/northwind/templates/” +
11: “listing9.15.xml?XmlDoc=” + Server.UrlEncode(xmlDoc);
12: XmlDocument doc = new XmlDocument();
13: doc.Load(url);
14: Response.Write(doc.DocumentElement.OuterXml);
15: Response.End();
16: }
17: </script>
18: <html>
19: <body>
20: <form runat=”server”>
21: <table cellpadding=”4”>
22: <tr>
23: <td>
24: <b>CustomerID:</b></td>
25: <td>
26: <asp:TextBox id=”CustomerID” Text=”DLWID”
27: runat=”server” />
28: </td>
29: </tr>
30: <tr>
31: <td>
32: <b>Name:</b>
33: </td>
34: <td>
35: <asp:TextBox id=”ContactName” Text=”Dan Wahlin”
36: runat=”server” />
37: </td>
38: </tr>
39: <tr>
40: <td>
41: <b>Company Name:</b>
42: </td>
SQL Server 2000, XML, and ASP.NET
389
CHAPTER 9
SQL SERVER
syntax. If data in the before view is different from the data in the after view, an update is per-
ASP.NET
formed to the appropriate fields. For example, if the before view of a record shows a value of
Paul Allsing for the ContactName field, but the after view shows a value of Lance Edwards,
the Updategram will know that the ContactName field needs to be updated because the values
have changed. If the before view contains data, but the after view is empty, the appropriate row
will then be deleted. To understand this more fully, let’s take a look at how Updategrams are
structured in Listing 9.17.
1: <ROOT xmlns:updg=”urn:schemas-microsoft-com:xml-updategram”>
2: <updg:sync>
XML for ASP.NET Developers
390
The first thing you should notice is that Updategrams look a lot like the XML templates dis-
cussed earlier in the chapter. They start with a root element tag and are followed by a name-
space declaration (line 1). However, instead of using the sql prefix, Updategrams use the updg
prefix and tie it to a URI of urn:schemas-microsoft-com:xml-updategram. Line 2 introduces
a new element tag named sync, which acts a container for the before and after views of the
data being evaluated. The sync block defines the boundaries for transactions. If any statements
within the sync block fail, the transaction will be rolled back. The before and after data views
are enclosed within the sync block using the before and after element tags (lines 3–9). The
table within the database that will be changed is used as the element name in both the before
and after data views (lines 4 and 7). The table name element is followed by attributes that list
the columns (and their respective values) that will be used to determine whether an insert,
update, or delete operation should be performed.
The table name may also be followed by an id attribute in the case of before views and an id
and/or at-identity attribute in after views. The id attribute can be used to identify elements
within the before and after data view sections in cases where more than one insert, update, or
delete is being performed in the Updategram. By manually specifying a unique id for each ele-
ment, the elements within the before and after data sections will be matched up properly.
The at-identity attribute can be used along with an attribute named returnid to return the
identity of a newly inserted record. Using this attribute is similar to calling @@Identity in a
stored procedure following the insertion of a new row.
Performing an insert, update, or delete to a database table using Updategrams is as simple as
providing the proper information to the before and after data sections. Table 9.5 gives an
overview of how to accomplish each operation using Updategrams.
Let’s take a look at performing an insert, update, and delete to the Customers table of the
Northwind database. Listings 9.18, 9.19, and 9.20 show each operation.
SQL Server 2000, XML, and ASP.NET
391
CHAPTER 9
Hitting the URL for the template shown in Listing 9.18 results in an insert being performed
into the Customers table of the Northwind database because data does not appear in the before
section but does appear in the after section. 9
SQL SERVER
ASP.NET
of a newly added row can be obtained by adding the returnid attribute to the sync element
and the at-identity attribute to the appropriate table element, as shown next:
<Root xmlns:updg=”urn:schemas-microsoft-com:xml-updategram”>
<updg:sync updg:returnid=”x”>
<updg:before>
</updg:before>
<updg:after>
<MyTable updg:at-identity=”x” Column1=”Some Value”
Column2=”Some Other Value”/>
</updg:after>
</updg:sync>
</Root>
XML for ASP.NET Developers
392
The identity of the newly added record will be returned in the following format:
<returnid>
<x>404</x>
</returnid>
Referring back to Table 9.5, you can see that an update is performed when there is data within
the before and after sections. Listing 9.19 first uniquely identifies a row in the Customers table
by listing the CustomerID attribute’s value in the before section of the template. The after sec-
tion contains only the name and value of the field in the database that should be updated.
Because the CustomerID value was provided in the before section, it is automatically used in
the after section. Although this example shows only one column being updated, others can be
listed as necessary.
Using Updategrams to perform deletes is as simple as providing the table name along with the
value of the table’s primary key in the before data section. By leaving the after data section
blank, the database knows that this row should be deleted.
SQL Server 2000, XML, and ASP.NET
393
CHAPTER 9
Although the examples shown to this point have shown how easy it is to work with Updategrams
to perform inserts, updates, or deletes, they haven’t been very practical. How many times have
you found it useful to hard-code a value into a SQL statement or stored procedure to perform an
insert, update, or delete? This is because insert, update, and delete operations are normally
designed to accept variable input parameters.
Like XML templates, Updategrams also allow parameters to be passed in. Listing 9.21 shows
how to use parameters in Updategrams to update a record.
Lines 2–5 take care of adding two input parameters named CustomerID and CompanyName.
These parameters are then dynamically added to the before and after data sections by append-
ing them with the $ character, as shown in lines 8 and 11. Passing the parameters into the
Updategram is accomplished in the same manner as passing parameters to regular template
9
SQL SERVER
ASP.NET
http://localhost/northwind/templates/listing9.21.xml?CustomerID=DLWID&
CompanyName=Wahlin+Consulting
Several more advanced aspects of Updategrams have not been covered in this section. For
example, multiple updates, inserts, or deletes can be performed in a single Updategram, and
updates involving multiple tables can be performed with the aid of mapping schemas.
Updategrams are a promising technology that can provide additional flexibility to ASP.NET
applications.
Updategrams can be especially useful in applications where end users do not always have con-
nectivity to the network or database (traveling sales representatives, for example). By storing
XML for ASP.NET Developers
394
the necessary database changes in the form of Updategram files, when the user can connect to
the network, any database changes can be sent and processed automatically by the SQL Server
2000 database. There are, of course, many other potential uses; the sky’s the limit!
LISTING 9.22 Using the ExecuteXmlReader() Method with FOR XML Queries
1: <%@ Import Namespace=”System.Data” %>
2: <%@ Import Namespace=”System.Data.SqlClient” %>
3: <%@ Import Namespace=”System.Xml” %>
4: <script language=”C#” runat=”Server”>
5: private void Page_Load(object sender, System.EventArgs e) {
6: Response.ContentType = “text/xml”;
7: string sql = “SELECT * FROM Customers FOR XML AUTO”;
8: string connStr =”server=localhost;uid=sa;pwd=;database=Northwind”;
9: SqlConnection sqlConn = new SqlConnection(connStr);
10: sqlConn.Open();
11: SqlCommand cmd = new SqlCommand(sql,sqlConn);
12: XmlReader reader = cmd.ExecuteXmlReader();
13: Response.Write(“<?xml version=\”1.0\” encoding=\”utf-8\”?>”);
14: Response.Write(“<root>”);
15: while (reader.Read()) {
16: Response.Write(“<” + reader.Name + “ “);
17: if (reader.HasAttributes) {
18: while (reader.MoveToNextAttribute()) {
19: Response.Write(reader.Name + “=\”” +
20: new StringBuilder(reader.Value).Replace(“&”,”&”) +
21: “\” “ );
22: }
23: }
SQL Server 2000, XML, and ASP.NET
395
CHAPTER 9
The code in Listing 9.22 creates well-formed XML that is simply written out to the browser.
This same type of process can be made more useful by dynamically loading the XML frag-
ment returned from SQL Server 2000 into an XPathDocument class and then transforming it
using XSLT. Listing 9.23 shows how this is accomplished.
SQL SERVER
16: SqlCommand cmd = new SqlCommand(sql,sqlConn);
17: XmlTextReader reader = (XmlTextReader)cmd.ExecuteXmlReader();
ASP.NET
18: //XPathDocument will add a root node for us
19: XPathDocument xmlDoc = new XPathDocument(reader);
20: XslTransform xslDoc = new XslTransform();
21: xslDoc.Load(Server.MapPath(“customers.xsl”));
22: xslDoc.Transform(xmlDoc,null,Response.Output);
23: sqlConn.Close();
24: if (reader != null) reader.Close();
25: }
26: </script>
XML for ASP.NET Developers
396
Because the SQL query can return many customer records, you would think that these records
need to be wrapped with a root node before the XML could be passed to the XPathDocument’s
constructor. Fortunately, the XPathDocument class will take care of adding a root node automat-
ically, making it easy to transform the XML via XSLT.
When namespace URIs not included in the XML fragment must be identified as the XML frag-
ment is parsed, the XmlNamespaceManager class can be created, filled, and passed in as an
argument. If this (along with other classes such as the XmlNameTable class) is not needed by
the application, NULL can be passed instead (in C#). For our purposes, we’ll focus specifically
on the docTypeName and internalSubset arguments. The docTypeName argument defines the
DOCTYPE name. The internalSubset argument defines one or more parts of a DTD that may
be referenced by the XML fragment.
The code shown in Listing 9.24 shows how the XmlParserContext class can be used to define
an entity named contact that is referenced by the XML fragment returned from the FOR XML
query. This entity definition is created in a string named entityString (line 10) that is added
to the constructor for the XmlParserContext class (lines 31–33). Any entity definitions in the
XML that are defined as &contact; will be resolved to a value of Anders.
SQL Server 2000, XML, and ASP.NET
397
CHAPTER 9
SQL SERVER
32: new XmlParserContext(null, null, “Customers”, null,
ASP.NET
33: null, entityString,””, “”, XmlSpace.None);
34: XmlValidatingReader xmlReader =
35: new XmlValidatingReader(xmlString,XmlNodeType.Element,
36: xmlContext);
37: xmlReader.ValidationType = ValidationType.None;
38: XmlDocument xmlDoc = new XmlDocument();
39: XmlElement root = xmlDoc.CreateElement(“Northwind”);
40: xmlDoc.AppendChild(root);
41: while (!xmlReader.EOF) {
42: root.AppendChild(xmlDoc.ReadNode(xmlReader));
43: }
44:
XML for ASP.NET Developers
398
Summary
SQL Server 2000 provides a rich set of useful features dedicated to working with data in the
form of XML. Through leveraging these features, layers of ASP.NET code can be eliminated.
Direct access to the database can be accomplished via URL queries, templates, XPath queries,
and through OPENXML and Updategrams. This allows XmlDocument, XPathDocument, or DataSet
objects to be loaded directly without going through the SQL managed provider. When used
with XSLT, this presents an efficient solution that can simplify your ASP.NET application
development.
In the next chapter, you’ll be presented with an in-depth look at Simple Object Access Protocol
(SOAP) and see how Web services can be created to expose your data or services as XML to
interested parties anywhere in the world. You’ll also see how you can utilize external Web ser-
vices to enhance your ASP.NET applications.
Working with ASP.NET, XML, CHAPTER
10
SOAP, and Web Services
IN THIS CHAPTER
Understanding SOAP 400
Understanding SOAP
Making calls to distributed objects to obtain data or perform specific functions has not been an
easy task in the past. For example, if your application needs to obtain address or credit card
information found on a remote system, accessing the appropriate object or objects on that sys-
tem has been somewhat problematic. Add the fact that many remote systems rely on a specific
vendor technology or language, and the problem is compounded.
Many alternatives to this problem have been presented over the years, including CORBA
(Common Object Request Broker Architecture), DCOM (Distributed Common Object Model),
and Java RMI (Remote Method Invocation), but none have resulted in simplifying remote
object calls without inventing some type of new technology or relying on vendor-specific tech-
nology. With the introduction of version 1.0 of the Simple Object Access Protocol (SOAP) in
September of 1999, a new mechanism for making Remote Procedure Calls (RPC) emerged that
combines the power of XML with the HTTP protocol. Version 1.1 of SOAP provides more
flexibility in working with other types of wire transport protocols, including FTP and SMTP.
In this chapter we’ll take a look at the importance of the SOAP specification and detail how
SOAP can be used in ASP.NET to provide applications with more power and flexibility. This
discussion includes information about the SOAP specification itself, alternatives to SOAP, and
what role SOAP plays in Web services. By the end of the chapter you’ll have a good under-
standing of how you can consume and expose a variety of services using SOAP along with
other description languages built in to the .NET platform. Before formally introducing SOAP,
let’s take a look at a few alternatives.
Alternatives to SOAP
The SOAP protocol is the latest in many attempts to simplify the process of making calls to
objects found in distributed environments. In this section we’ll take a high-level look at a few
of the alternative protocols to give you a better perspective on why the SOAP protocol is so
promising. Let’s start off by examining a protocol many Microsoft developers are already
familiar with: DCOM.
considered to be a scalable solution because of how it handles garbage collection and connec-
tion management. DCOM clients instantiate a connection to a remote object by using a proxy.
After a reference to the object has been obtained, clients send ping messages to let the object
know that they are still acting as a consumer. This creates scalability problems as the number
of clients sending ping messages across the network increases.
DCOM also presents challenges dealing with firewalls and state management. With regard to
firewalls, if the firewall administrator has not opened ports used to pass through DCOM mar-
shaling calls, the calls will likely be blocked and therefore fail before reaching the intended
recipient object. DCOM also relies on state management to know when a particular client is no
longer connected (the pinging mechanism). This makes using it in a stateless arena such as the
Web more difficult.
For more information concerning DCOM, visit http://www.microsoft.com/com/tech/
dcom.asp.
tion because of its capability to use HTTP tunneling. However, because it focuses on the Java
WEBSERVICES
SOAP, AND
language, it doesn’t present an optimal solution for exposing services to clients written using
other languages.
For more information concerning Java RMI, visit http://java.sun.com/products/jdk/rmi/.
XML Related Technologies
402
XML-RPC
XML-RPC was one of the first specifications to use XML structures in making remote calls. It
allows calls to ride on top of HTTP, which allows them to pass through firewalls on port 80.
The XML-RPC Specification revolves around the HTTP POST to transmit XML structures to
and from different distributed systems. Many high-level concepts described by XML-RPC have
been adopted into the SOAP protocol. An example of a simple remote method call using XML-
RPC is shown next:
<?xml version=”1.0”?>
<methodCall>
<methodName>GetName</methodName>
<params>
<param>
<value>
<string>ALFKI</string>
</value>
</param>
</params>
</methodCall>
Looking at this, you can see that it is an extremely verbose way of marking up or “serializing”
a method call. On the other hand, it’s very easy to understand and parse. However, you’ll see in
the next few sections that SOAP extends many of the XML-RPC concepts and minimizes some
of the more verbose XML-based calls. SOAP also refines data typing by leveraging the W3C
Schema Part II specification. Although XML-RPC does present a good starting point for using
XML in distributed environments, it has several areas that can be improved on. Sometimes
more is less. This is especially true when it comes to transmitting data over the wire with
HTTP.
For more information concerning XML-RPC, including information on where to download
XML-RPC–compatible implementations for a variety of platforms, visit http://
www.xmlrpc.com.
What Is SOAP?
Aside from XML-RPC, the SOAP alternatives previously listed (as well as many others that
were not covered) rely on complex distributed object architectures installed on tightly inte-
grated systems. SOAP provides a much simpler architecture. In fact, SOAP is simply a wire
protocol that can be used to transfer information between a client and a server using XML to
maintain structure. It does not rely on complex object architectures to make calls between
distributed objects. Rather, SOAP provides a framework for exchanging messages between
distributed environments without relying on a particular operating system, a programming
Working with ASP. NET, XML, SOAP, and Web Services
403
CHAPTER 10
language, or an object model architecture. The authors of the SOAP version 1.1 protocol pro-
vide the following definition:
SOAP provides a simple and lightweight mechanism for exchanging structured and typed infor-
mation between peers in a decentralized, distributed environment using XML. SOAP does not
itself define any application semantics such as a programming model or implementation spe-
cific semantics; rather it defines a simple mechanism for expressing application semantics by
providing a modular packaging model and encoding mechanisms for encoding data within
modules. This allows SOAP to be used in a large variety of systems ranging from messaging
systems to RPC.
One of the design goals first identified by the SOAP authors was to develop a protocol that
invented no new technology. After all, with the rise of XML and HTTP over the years, an
excellent foundation already existed that could be leveraged by the SOAP authors. As a result,
SOAP relies on XML and a variety of other protocols including HTTP to exchange calls
between objects.
SOAP works by serializing object calls into an XML structure that can then be passed to
another machine using a protocol such as HTTP. “What is serialization?” you ask. You saw a
simple example of this in the XML-RPC section, but let’s take a look at a more abstract sam-
ple. Assume a distributed object exposes the following method that is used to get a customer
name from a database:
GetName(int CustomerID) {}
Serializing this method call into an XML structure could result in the following XML
fragment:
<callAMethod>
<GetName>
<params>
<CustomerID type=”int”>1234</CustomerID>
</params>
</GetName>
</callAMethod >
As shown, serialization simply takes a given object and “serializes” particular aspects of the
object into a structure that can be easily transported to a remote system. After the remote sys-
tem receives it, processing must be done to extract the information, instantiate the correct
object, make the appropriate method call, and return a response to the caller. 10
ASP, NET, XML,
Although the previous serialized XML structure may work for your own objects, how do other
WEBSERVICES
SOAP, AND
objects know how to work with it? And if the remote object is required to return a response,
what structure should the response follow? This is where the SOAP protocol comes into play.
XML Related Technologies
404
It provides a standard way to serialize calls made to remote objects and retrieve results
returned by the object. This allows any application with an understanding of SOAP to be
accessed remotely, no matter what language or operating system it relies on. Imagine the
possibilities!
Before you jump into more specifics on the SOAP protocol, Listing 10.1 provides a quick
look at how the GetName() method shown earlier can be serialized into a valid SOAP request
structure.
LISTING 10.1 Serialization of the GetName() Method into a SOAP Request Structure
1: <soap:Envelope xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
2: xmlns:xsd=”http://www.w3.org/2001/XMLSchema”
3: xmlns:soap=”http://schemas.xmlsoap.org/soap/envelope/”>
4: <soap:Body>
5: <GetName xmlns=”Some-URI”>
6: <CustomerID>ALFKI</CustomerID>
7: </GetName>
8: </soap:Body>
9: </soap:Envelope>
Details on the different pieces used to create this SOAP request follow in the next few sections.
After learning these details, you’ll see how SOAP can be used with Web services to enhance
your ASP.NET applications.
NOTE
Although the next few sections provide a detailed look at the different pieces that
make up a SOAP request or response, a more thorough look at SOAP is provided by
the book Understanding SOAP (ISBN 0-672-31922-5) by Kennard Scribner and Mark
Stiver, Sams Publishing.
SOAP messages can contain some or all of the following elements, depending on the function-
ality that a given SOAP structure includes:
• Envelope
• Header
• Body
• SOAP namespaces
• Attributes
• SOAP fault
To fully understand these elements and how they relate to each other in a SOAP message,
Listing 10.2 presents the schema that a SOAP message follows. Each of the items listed in the
schema will be discussed in turn in the next few sections.
NOTE
The schema shown in Listing 10.2 is based on an older schema specification. It is
shown here only to help you understand the structure and contents of SOAP mes-
sages, not to demonstrate how to create XML schemas.
1: <?xml version=”1.0”?>
2: <!-- XML Schema for SOAP v 1.1 Envelope -->
3: <!-- Copyright 2000 DevelopMentor,
4: International Business Machines Corporation,
5: Lotus Development Corporation, Microsoft, UserLand Software -->
6: <schema xmlns=’http://www.w3.org/1999/XMLSchema’
7: xmlns:tns=’http://schemas.xmlsoap.org/soap/envelope/’
8: targetNamespace=’http://schemas.xmlsoap.org/soap/envelope/’>
9:
10: <!-- SOAP envelope, header and body -->
11:
12: <element name=”Envelope” type=”tns:Envelope”/>
13: <complexType name=’Envelope’>
14: <element ref=’tns:Header’ minOccurs=’0’/> 10
ASP, NET, XML,
Let’s first take a look at the wrapper around SOAP messages: the Envelope.
<soap:Envelope
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:xsd=”http://www.w3.org/2001/XMLSchema”
xmlns:soap=”http://schemas.xmlsoap.org/soap/envelope/”>
<soap:Header>
<!-- Header section is optional -->
</soap:Header>
<soap:Body>
<!-- Mandatory section. An object’s serialized information goes here -->
</soap:body>
</soap:Envelope>
As shown, the Envelope element acts as a container for all content found within a SOAP mes-
sage. Notice that the element contains a SOAP namespace named soap that points to a URI of
http://schemas.xmlsoap.org/soap/envelop/ (the actual location of the SOAP message
schema). It also contains namespace declarations relating to the XML schema specification
and the schema instance.
Although Envelope element attributes are optional, any attribute that does appear in the
Envelope element must be namespace qualified. Before proceeding, let’s take a more in-depth
look at Envelope namespaces and attributes.
10
SOAP Envelope Namespaces and Attributes
ASP, NET, XML,
WEBSERVICES
SOAP, AND
The previous example of the SOAP message structure included the soap namespace. Looking
back at Listing 10.2, you’ll notice that the Envelope element definition includes the
<anyAttribute/> tag (line 17). This allows for custom attributes to be added to the
XML Related Technologies
408
Envelope element as necessary. As mentioned earlier, these attributes must be namespace qual-
ified to be included in the SOAP message.
With regard to namespaces, an interesting aspect of the soap namespace shown in the previous
example is that it does not specify what version of SOAP we are working with. This is accord-
ing to the SOAP version 1.1 specification. The specification states the following:
SOAP does not define a traditional versioning model based on major and minor version num-
bers. A SOAP message MUST have an Envelope element associated with the “http://
schemas.xmlsoap.org/soap/envelope/” namespace. If a message is received by a SOAP
application in which the SOAP Envelope element is associated with a different namespace, the
application MUST treat this as a version error and discard the message. If the message is
received through a request/response protocol such as HTTP, the application MUST respond
with a SOAP VersionMismatch faultcode message (see section 4.4) using the SOAP
“http://schemas.xmlsoap.org/soap/envelope/” namespace.
What does this tell us about the namespace URI? To send properly structured SOAP messages,
the namespace URI must be included on the Envelope element exactly as shown:
http://schemas.xmlsoap.org/soap/envelope/
Keep in mind that as the SOAP specification goes through the W3C working group process,
the namespace may eventually change. But for now, changing the URI will result in the remote
application returning a faultcode message (discussed later). Although the example shown ear-
lier used the soap namespace prefix, you are free to use your own prefix name as long as it
points to the proper URI.
This example shows how independent elements such as the Transaction element must be
namespace qualified. Assuming the remote object being called understands the data contained
within the Transaction element, the appropriate action can be taken. What happens if the
object doesn’t understand how to deal with transactions properly? Should it go ahead and
process the request? This is where the mustUnderstand attribute comes into play.
The global nature of these attributes allows them to be applied to any complexType definition
within a SOAP message, meaning that it can be used on any element. However, they will nor-
mally be used within the Header element section of a SOAP message.
When the mustUnderstand attribute is used on the Header element, the recipient of the mes-
sage must “understand” how to process the information contained within the Header element.
Because incorrect return values may result from a recipient that does not understand a particu-
lar section contained within the Header element, the mustUnderstand attribute provides a
safety net. The SOAP specification says that any recipient that cannot understand the informa-
tion within the Header element when the mustUnderstand attribute is true (1) must fail to
process the message and return the SOAP-ENV:MustUnderstand fault (discussed later). If this
attribute is not present, it defaults to a value of false (0).
The actor attribute is used in cases where a SOAP message may pass through one or more
intermediaries before reaching the final destination recipient. This attribute can contain a URI
value type that is used to identify the recipient of the Header element. The specification pro-
vides a special URI with a value of http://schemas.xmlsoap.org/soap/actor/next for
cases where a SOAP message header is intended for the first SOAP application that processes
the message. A Header element containing no actor attribute automatically infers that the 10
recipient is the end point or final recipient in the SOAP message path.
ASP, NET, XML,
WEBSERVICES
SOAP, AND
An intermediary serves the dual purpose of receiving and forwarding SOAP messages. As a
SOAP message starts along the path toward the final recipient, some of the message’s header
information may be directed to a particular intermediary involved in the transferal process.
XML Related Technologies
410
After an intermediary receives a message with information directed to them (identified by the
actor attribute’s URI value), the SOAP specification states that they may not pass this infor-
mation along to other destinations in the path. This means that they may not forward the
Header element after they’ve consumed it. The specification does allow for intermediaries to
insert a new header element to pass along, but the “contract” for this newly added element will
be between the intermediary and the intended recipient. The originator of the SOAP message
will not have any knowledge nor ever see the newly added header.
Looking at the previous SOAP message, you can see that the GetName() method introduced
earlier in the chapter is being called. The method name is listed immediately following the
Working with ASP. NET, XML, SOAP, and Web Services
411
CHAPTER 10
Body element and is namespace qualified. The CustomerID parameter is then listed as a child
element of the method name and contains the data to be passed to the method.
Listing 10.3 contains a slightly more complex method call. The method being serialized is
shown next:
GetOrders(string CustomerID,int SalesRepID,float OrderMax)
The Body element structure for this method call is very similar to the structure shown earlier
aside from the additional parameters.
7: <return>
WEBSERVICES
SOAP, AND
8: <Orders>
9: <Order orderID=”1453” quantity=”1” />
10: <Order orderID=”1672” quantity=”3” />
XML Related Technologies
412
Notice that the method name has been appended with the word “Response” to identify
this SOAP message as a response. Immediately under the method response element
(GetOrdersResponse), the return element is used to encapsulate the SOAP response.
This example shows how data records can be serialized into an XML structure that can then
be sent back to the calling SOAP application. A more simple SOAP response may have only
a single return value, as follows:
<return>1582</return>
The return element must always be the first one listed under the method response element. If
the method also contains out parameters, these elements may then follow the return element
in the SOAP body.
When an error occurs, the SOAP application is required to place the Fault element as the first
child of the Body element in the SOAP message. The Fault element may have some or all of
the child elements listed in Table 10.1, depending on the nature of the error that occurred.
Children of the detail element are referred to in the SOAP version 1.1
WEBSERVICES
SOAP, AND
ID false
WEBSERVICES
SOAP, AND
IDREF false
ENTITY false
XML Related Technologies
416
The SOAP specification allows for single and multiple references to a particular type. A single
reference occurs when information appears only once within a SOAP message. All the previ-
ous SOAP message examples up to this point demonstrated single references. A multiple
reference occurs when the same piece of information occurs more than once within a SOAP
message. For example, if a string listed in two parameters contains the same value, each para-
meter can instead refer to a reference as shown next. Doing this conserves space by making the
serialized call less verbose, especially when an information item may appear multiple times
within a SOAP message:
<soap:Envelope
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns:xsd=”http://www.w3.org/2001/XMLSchema”
xmlns:soap=”http://schemas.xmlsoap.org/soap/envelope/”>
<soap:Body>
<GetName xmlns=”Some-URI”>
<CustomerID href=”#param”/>
<CustomerRef href=”#param”/>
</GetName>
<getNameData id=”param” xsi:type=”xsd:string”>
ALFKI
</getNameData>
</soap:Body>
</soap:Envelope>
Working with ASP. NET, XML, SOAP, and Web Services
417
CHAPTER 10
This example shows how the id and href attributes can be used to allow multiple parameters
to reference the same piece of information.
Notice that the array’s structure is qualified by the SOAP-ENC namespace (SOAP Encoding)
and has an attribute named arrayType. This attribute’s value specifies the type of array and the
number of members. After this information is specified, the actual members of the array are
listed sequentially. The array structure is referenced by using the href and id attributes as
shown earlier.
Serialized arrays can also be defined as sparse arrays, partially transmitted arrays, and multi- 10
dimensional arrays. Examples of these are shown next:
ASP, NET, XML,
WEBSERVICES
SOAP, AND
Sparse Array:
<SOAP-ENC:Array SOAP-ENC:arrayType=”xsd:string[,][4]”>
<SOAP-ENC:Array SOAP-ENC:position=”[2]”
XML Related Technologies
418
SOAP-ENC:arrayType=”xsd:string[10,10]>
<item SOAP-ENC:position=”[2,2]”>Third row, third col</item>
<item SOAP-ENC:position=”[7,2]”>Eighth row, third col</item>
</SOAP-ENC:Array>
</SOAP-ENC:Array>
Multidimensional Arrays:
<SOAP-ENC:Array SOAP-ENC:arrayType=”xsd:string[][2]”>
<item href=”#array1”/>
<item href=”#array2”/>
</SOAP-ENC:Array>
<SOAP-ENC:Array id=”array1” SOAP-ENC:arrayType=”xsd:string[2]”>
<item>r1c1</item>
<item>r1c2</item>
<item>r1c3</item>
</SOAP-ENC:Array>
<SOAP-ENC:Array id=”array2” SOAP-ENC:arrayType=”xsd:string[2]”>
<item>r2c1</item>
<item>r2c2</item>
<item>r2c3</item>
</SOAP-ENC:Array>
In addition to arrays, the SOAP specification also allows compound types such as structs to be
serialized. The specification defines a struct as “a compound value in which accessor name is
the only distinction among member values, and no accessor has the same name as any other.”
As an example, the SOAP version 1.1 specification provides the following example of serializ-
ing a struct named Book:
<e:Book>
<author>Henry Ford</author>
<preface>Prefatory text</preface>
<intro>This is a book.</intro>
</e:Book>
</e:Book>
<e:Person id=”Person-1”>
<name>Henry Ford</name>
<address xsi:type=”m:Electronic-address”>
<email>mailto:[email protected]</email>
<web>http://www.henryford.com</web>
</address>
</e:Person>
<e:Person id=”Person-2”>
<name>Samuel Crowther</name>
<address xsi:type=”n:Street-address”>
<street>Martin Luther King Rd</street>
<city>Raleigh</city>
<state>North Carolina</state>
</address>
</e:Person>
Finally, both arrays and structs can coexist in a SOAP message structure:
<xyz:PurchaseOrder>
<CustomerName>Henry Ford</CustomerName>
<ShipTo>
<Street>5th Ave</Street>
<City>New York</City>
<State>NY</State>
<Zip>10010</Zip>
</ShipTo>
<PurchaseLineItems SOAP-ENC:arrayType=”Order[2]”>
<Order>
<Product>Apple</Product>
<Price>1.56</Price>
</Order>
<Order>
<Product>Peach</Product>
<Price>1.48</Price>
</Order>
</PurchaseLineItems>
</xyz:PurchaseOrder>
information and methods specific to a request or response. These headers provide necessary
WEBSERVICES
SOAP, AND
functionality that allows SOAP messages to be transported to and from distributed systems
using HTTP. Within the headers, three methods can be used— GET, POST, and M-POST.
Headers can also contain Content-Length, Content-Type, and name/value pair information spe-
cific to the request or response.
XML Related Technologies
420
The following example shows a simple HTTP POST request that contains the mandatory
SOAPAction field. According to the SOAP version 1.1 specification, this field must be included
within HTTP requests to allow firewalls to appropriately filter SOAP messages sent via HTTP.
The value of this field normally contains a namespace URI followed by the # character and the
name of the method being called within the SOAP body. A firewall can then match the name-
space URI and method name with those specified within the SOAP message:
POST /StockQuote HTTP/1.1
Content-Type: text/xml; charset=”utf-8”
Content-Length: nnnn
SOAPAction: “Some-URI#GetName”
An HTTP response looks similar, although the status of the response needs to be returned to
the client. In this case a success code of 200 is being returned, although you’re more than
likely quite familiar with other return codes such as the infamous 404 Not Found error code:
HTTP/1.1 200 OK
Content-Type: text/xml; charset=”utf-8”
Content-Length: nnnn
</GetOrdersResponse>
</soap:Body>
</soap:Envelope>
Web services allow you to work with objects and receive data in a nicely formatted XML
structure. This means that you don’t have to employ costly string-parsing routines or resort
XML Related Technologies
422
to other screen-scraping methods. You simply invoke the remote object by sending a serialized
request, get a response, deserialize the response message, and utilize that response data as
appropriate.
NOTE
To see just how much work can be involved when screen scraping an HTML file to
gather the required data, take a look the following articles: http://
www.asptoday.com/articles/19991229.htm http://www.asptoday.com/
articles/19991231.htm.
HTTP-GET
You have more than likely been exposed to working with the GET verb in creating classic ASP
applications that utilize forms containing the method=”get” attribute. Forms that use this
method will pass all form values as name/value pairs on the QueryString. For example, a Web
form containing first name and last name as inputs could yield the following QueryString
when submitted:
http://www.someServer.com/processForm.aspx?firstname=Dan&lastname=Wahlin
Using C# and ASP.NET, you can extract name/value pairs found in the QueryString by doing
the following:
<script language=”C#” runat=”Server”>
public void Page_Load(Object sender, EventArgs E) {
Response.Write(“First Name: “ + Request.QueryString[“firstname”]);
Response.Write(“<br />”);
Response.Write(“Last Name: “ + Request.QueryString[“lastname”]);
}
</script>
Working with ASP. NET, XML, SOAP, and Web Services
423
CHAPTER 10
When used with Web services, a QueryString containing the necessary name/value pairs can be
passed to the service. In response, the XML structure will contain return values wrapped
within XML elements that specify the value’s data type. For example, the following structure
could represent a response to an HTTP-GET request when first and last names are passed to a
Web service that returns the number of orders placed:
<?xml version=”1.0”?>
<int xmlns=”http://tempuri.org/”>4</int>
Although HTTP-GET requests are limited to name/value pairs, HTTP-GET does support returning
any COM+ type as a return value. This means that types such as arrays, structs, DataSets, and
arrays of structs can be returned. The following call to a Web service shows how an array of
names can be sent using name/value pairs:
http://www.someSite.com/arrays/returnArray?name=Dan&name=Todd&name=Elaine&
name=Danny&name=Michelle
HTTP-POST
The HTTP-POST verb works in a manner similar to the HTTP-GET, except that the name/value
pairs are not passed using the QueryString. Instead, this information is UUEncoded and passed
within the body of the message sent to the Web service. Like HTTP-GET requests, the response
contains return values wrapped within XML elements that specify the value’s data type.
It’s important to note that both HTTP-GET and HTTP-POST Web service requests and responses
are restricted to using name/value pairs. This is very limiting when more robust data types
need to be sent. When these types are needed, SOAP must be used because it provides built-in
support for sending complex types, as shown earlier in the chapter.
Web services provide a fairly simple architecture that can be consumed to add additional func-
WEBSERVICES
SOAP, AND
tionality and power to ASP.NET applications. Using the .NET framework, Web services can
also be written that non-Windows clients can consume using a variety of languages. Most of
the more difficult parts associated with using Web services, including the serialization and
XML Related Technologies
424
deserialization of messages, occurs behind the scenes through using utilities provided by the
.NET platform. These utilities (discussed later) automatically create the appropriate support
files to expose or consume a Web service.
So what pieces are needed for Web services to become functional? On the client side, a proxy
can be created that allows the client application to serialize and send messages to the Web
service. This proxy must also be able to deserialize responses sent back by the Web service.
Before this proxy can be created, however, the client must know where to find the Web service
and know what interface(s) the Web service exposes, including what parameters must be
passed to it.
After these resources are known, the client proxy can be created and a request message can be
sent to the Web service, which can then send back the appropriate response. The relationship
between the client and the Web service is depicted in Figure 10.1. Notice that because mes-
sages are transmitted using HTTP and SOAP, firewalls become much less of an issue when
compared to other technologies such as DCOM and CORBA.
WSDL
Response
Client Web Service
Request
Proxy
UDDI
Firewall Firewall
FIGURE 10.1
Web service architecture.
In the next section, you’ll learn how to build the Web service portion shown in Figure 10.1.
information that attributes contain can be retrieved at run time through reflection. You can use
predefined attributes or you can define your own custom attributes.
Using attributes and processing directives along with the System.Web.Services namespace,
Web services can be constructed quite easily and existing ASP.NET code can quickly be con-
verted into a Web service that can be consumed by others. The main class used for Web
service specific attributes is named WebMethodAttribute. It contains the properties listed in
Table 10.4.
Property Description
BufferResponse Determines whether the response from the Web service request is
buffered. Setting this property to true (the default) serializes the
Web service response into a buffer. After the entire response is
serialized, it is returned to the client. This property should be set to
false only when large messages must be serialized.
CacheDuration Determines the number of seconds the Web service response
should be held in the cache. The default value is 0, which results in
no caching at all. In cases where a Web service’s return value may
not need to be completely dynamic, using this property can result
in increased performance and efficiency. For large responses, turn-
ing on caching can reduce available memory.
Description This is useful for describing the Web service and methods it
exposes on the service description page.
EnableSession Determines if session state is enabled for a Web service method.
The default is false.
MessageName This property provides the name used for the Web service method
in the data passed to and returned from a Web service method. It
can be useful for aliasing methods and properties and when over-
loaded methods exist in a Web service class.
TransactionOption Used when a Web service method needs transaction support. This
property is disabled by default. Values can be members of the
TransactionOption enumeration:
• Disabled
• NotSupported 10
ASP, NET, XML,
• Supported
WEBSERVICES
SOAP, AND
• Required
• RequiresNew
XML Related Technologies
426
Another class, named WebServiceAttribute, can also be used to provide a description of the
Web service and to provide a namespace in which the Web service operates. By walking you
through several examples, the next few sections show you how to use these attribute classes
and their properties.
VB.NET wraps the WebMethod attribute with < and > brackets and places it before the method
definition:
<WebMethod> Public Function MyMethod() as Integer
End Function
By applying this attribute, the method is made publicly accessible through HTTP-GET, HTTP-
POST, or SOAP requests. Aside from attributes, code destined to be used as a Web service must
also use a special processing directive named WebService. The use of this directive as well as
the WebMethod attribute is shown in Listing 10.6. This listing simply returns a string value
based on the input that is provided.
The WebService directive must be the first line of your Web service. It lets the compiler know
what type of component it is so that it can be compiled properly. Although the code references
a class within the asmx file, it could reference another class stored in the \bin directory, if
desired, by listing the namespace that the external class resides in:
<%@ WebService Language=”C#” Class=”Greeting.Greet” %>
Working with ASP. NET, XML, SOAP, and Web Services
427
CHAPTER 10
[WebService(Namespace=”http://www.TomorrowsLearning.com/Greeting”,
Description=”This Web Service responds by saying ‘Hi’”)]
class Greeting: WebService {
[WebMethod(Description=”This method says ‘Hi’ and appends the input “ +
“param to the response”)]
public string Greet(string name) {
return “Hi “ + name + “!”;
}
}
Failing to uniquely identify the second Greet() method by using the MessageName property
would result in the following error:
Both System.String Greet(System.String, System.String) and System.String
Greet(System.String) use the message name ‘Greet.’ Use the MessageName property
of the WebMethod custom attribute to specify unique message names for the
methods.
Notice that to use the TransactionOption property, you must reference the System.
EnterpriseServices assembly and namespace.
2: using System;
3: using System.Web.Services;
4:
XML Related Technologies
430
Because the Counter class has been subclassed, it now has access to Application state.
In cases where access to session state is needed with a Web service, the code shown in Listing
10.10 can be used:
Line 8 shows a new attribute named EnableSession that is set to true. Because Session state
will oftentimes not be needed in Web services, its default value is false. It’s important to
keep in mind that state management is not a part of any Web service specification. Because
your Web services can be hit by a variety of clients on a multitude of platforms, it is recom-
mended that you use state-management techniques (such as the Session object) sparingly and
keep your Web services stateless when possible.
10
ASP, NET, XML,
WEBSERVICES
SOAP, AND
FIGURE 10.2
The Web service test screen.
XML Related Technologies
432
Without any coding on your part, the asmx file is dynamically compiled and a test page is gen-
erated. To try out the Greeting Web Service, click the Greet link shown in Figure 10.2, and on
the next screen enter a name into the input box and hit the Invoke button. Doing this should
display a result similar to that shown in Figure 10.3.
FIGURE 10.3
Invoking a Web service.
TIP
A template file named DefaultWsdlHelpGenerator.aspx is used to generate the Web
service test screen shown in Figure 10.2. By editing this file, you can easily customize
the test screen to fit into the look and feel of your Web site.
This problem is solved through using an XML grammar titled Web Service Description
Language, or WSDL. WSDL provides consumers of the Web service with detailed information
about binding to the service. It also details what methods are accessible and the parameters and
data types expected by those methods. The data type(s) of the response issued from each Web
service method are also described.
So how is all this information structured? Using XML syntax, of course. Looking back to the
test Web service page shown in Figure 10.2, you’ll notice that a link is provided to the WSDL
document that describes the Greeting Web service. Clicking this link results in the document
shown in Listing 10.11.
NOTE
Direct access to a given Web service’s WSDL document can be obtained by adding
?WSDL after the name of the asmx file. For example, the following URL will pull up
the WSDL document for the Web service shown in Listing 10.6:
http://localhost/testbed/Chapter10/listing10.6.asmx?WSDL
20: </s:sequence>
21: </s:complexType>
XML Related Technologies
434
106: </output>
WEBSERVICES
SOAP, AND
107: </operation>
108: </binding>
109: <service name=”Greeting”>
XML Related Technologies
436
This document describes three ways for interacting with the Web service:
• SOAP
• HTTP-GET
• HTTP-POST
Although each of the three sections described in the WSDL document exhibit different ways of
describing the service, they all serve the purpose of providing the structure for the Web service
request and response.
The <type> Element
The type element serves as a container for an XSD schema that is used to define parameter
names and data types used in the Web service. The schema it wraps is referenced by other ele-
ments within the WSDL document, as shown in the next few sections.
The <message> Element
The message element is used to describe the content of the messages sent to and from the Web
service. For the SOAP request, lines 34–39 are used to describe the input and response para-
meters using the message and part elements:
34: <message name=”GreetSoapIn”>
35: <part name=”parameters” element=”s0:Greet” />
36: </message>
37: <message name=”GreetSoapOut”>
38: <part name=”parameters” element=”s0:GreetResponse” />
39: </message>
Working with ASP. NET, XML, SOAP, and Web Services
437
CHAPTER 10
Looking at this section of code, you’ll see that each part element references an element
described in the XSD schema contained within the type element (lines 11–33). This allows
parameter names and data types to be defined using the XML schema standard.
The <portType> Element
The portType element defines the different operations that are supported by the Web service
along with the message(s) involved with each operation. Looking at lines 52–57 (shown
below), you will see that the message elements are referenced by a adding a message attribute
to elements named input and output. These elements are wrapped by an operation element
that defines the name of the method being called (Greet, in this example). All this information
is wrapped by a portType element that has a name attribute:
52: <portType name=”GreetingSoap”>
53: <operation name=”Greet”>
54: <input message=”s0:GreetSoapIn” />
55: <output message=”s0:GreetSoapOut” />
56: </operation>
57: </portType>
In line 70, the binding element’s type attribute references the GreetingSoap portType 10
ASP, NET, XML,
defined earlier. Lines 71 and 72 define the type of transport to be used (HTTP) and the “style”
WEBSERVICES
SOAP, AND
of the SOAP message. For this example a document style is being defined which lends itself
well to XML documents being embedded in SOAP messages. The alternative is rpc, which
would actually work fine with this simple Web service.
XML Related Technologies
438
The soap:body element appears within the input and output elements and serves the purpose
of describing the content within the body of the SOAP message. This element can have use,
encodingStyle, parts, and namespace attributes. The use attribute determines whether the
content of the body is encoded. Optionally, a soap:header element could be included as a
child of the input and output elements as well.
The <service> Element
The service element references the different portType elements defined in the WSDL document
and provides the address for accessing each one. When Web services expose multiple methods,
several different portType and service elements may exist.
109: <service name=”Greeting”>
110: <port name=”GreetingSoap” binding=”s0:GreetingSoap”>
111: <soap:address
112: location=”http://localhost/TestBed/Chapter10/listing10.6.asmx” />
113: </port>
114: <port name=”GreetingHttpGet” binding=”s0:GreetingHttpGet”>
115: <http:address
116: location=”http://localhost/TestBed/Chapter10/listing10.6.asmx” />
117: </port>
118: <port name=”GreetingHttpPost” binding=”s0:GreetingHttpPost”>
119: <http:address
120: location=”http://localhost/TestBed/Chapter10/listing10.6.asmx” />
121: </port>
122: </service>
Now that you see how a client can learn about what capabilities a Web service has, it’s time to
see how to consume the services offered by the Web service.
NOTE
You can find the WSDL specification at http://msdn.microsoft.com/xml/general/
wsdl.asp.
the Web service to marshal data back and forth between the client and the Web service. The
client application actually knows nothing about the remote Web service. Instead, it treats the
proxy as if it were the real object performing the task. In turn, the proxy contacts the Web ser-
vice and then passes the response back to the client. The process of creating a client proxy is
shown next.
Table 10.5 shows the switch options documented in the .NET SDK documentation.
To generate a proxy for the Web service shown back in Listing 10.6, the following command-
line code can be used. Because no language switch is specified, C# code will be generated by
default:
wsdl /out:greetingProxy.cs
➥http://localhost/testbed/chapter10/listing10.6.asmx?WSDL
➥/n:Greeting
Executing this statement on the command line will result in the creation of the C# document
shown in Listing 10.12.
Working with ASP. NET, XML, SOAP, and Web Services
441
CHAPTER 10
Although the specifics of this file will not be discussed, you can see that the code specifies the
URL of the Web service (line 30) and then emulates the remote Web service object by creating
a Greet() method structure (lines 41–45). Depending on how complex the Web service is that
you need to create a proxy for, you can see that the WSDL utility is a big time-saver after you
know how to work with the switches!
To compile the proxy code into an assembly that can be used within an ASP.NET page, the fol-
lowing statement can be run from the command line (C# compile switches shown):
csc /t:library /r:system.web.services.dll /r:system.xml.dll
➥/out:d:\inetpub\wwwroot\testbed\bin\greetingProxy.dll greetingProxy.cs
TIP
Although not shown in this section, the WSDL.exe utility can also be used to create a
WSDL file and perform other tasks. This is accomplished by using the switches shown in
Table 10.5.
On entering a value into the text box and hitting the Submit button, the submitButton_Click
event handler is called (line 2). This handler instantiates the proxy created earlier and then
passes the value in the text box to the Greet() method (lines 3 and 4). From here, the proxy
takes care of serializing and deserializing the request and response message to and from the
Web service. This is shown in Figure 10.4.
10
ASP, NET, XML,
WEBSERVICES
SOAP, AND
FIGURE 10.4
Hitting a Web service from an ASP.NET page.
XML Related Technologies
444
Now that you’ve seen all the pieces that come into play when building or consuming a Web
service, the following section provides a simple example of how Web services can be used to
solve legitimate business problems.
between the companies, Wahlin’s Widgets is not able to provide customers with instant feed-
back on whether a particular product is in stock. As a result, customer orders for products that
are not currently in stock at the distribution center may be delayed for lengthy amounts of
time. When this situation occurs, Wahlin’s Widgets receives notification from an ACME distri-
bution center and lets the customer know via e-mail that the shipment of their product may be
delayed. Although this methodology works, there have been many customer complaints and
order cancellations because of unsatisfied customers.
To remedy this situation, Wahlin’s Widgets has worked with ACME Distribution to develop a
new strategy for checking inventory in a more timely fashion. The two companies have also
agreed to provide customers with the capability to check the status of orders and double-check
that orders were shipped to the proper address. Because ACME Distribution acts as the distrib-
ution center for several other companies, it has agreed to create a Web service that can be used
to check product availability and shipping status so that customers can get immediate feed-
back.
This Web service exposes several methods that return records from the ACME database,
including:
• CheckStock()
• GetOrders()
• GetProducts()
• GetProductByID()
• GetProductByName()
8: ShowHidePanels(“pnlSplash”);
9: } else {
10: ShowHidePanels(“”);
XML Related Technologies
448
Because Wahlin’s Widgets has integrated the Web service into its Web site, customer satisfac- 10
tion has increased as customers hitting the site can check whether a particular product is in
ASP, NET, XML,
WEBSERVICES
SOAP, AND
stock before ordering and can check on the status of any pending orders. Figure 10.5 shows the
results of checking the availability of a product on the Wahlin’s Widgets Web site.
XML Related Technologies
450
FIGURE 10.5
Integrating ACME’s Web service into the Wahlin’s Widgets Web site.
Many important aspects of Web services are not shown in this example. These range from
security implementations to asynchronous calls. Before creating or consuming any Web ser-
vice, it is highly recommended that you examine these and other Web service features. To
see a more full-featured demonstration of a Web service in action, visit http://
www.TomorrowsLearning.com and examine the golfer Tee-Times service.
Summary
As more and more distributed systems enter the network of computers throughout the world,
the capability to send messages that are used to programmatically manipulate remote objects
will become increasingly necessary. In this chapter you were provided with an in-depth look at
the SOAP specification and learned how SOAP messages are structured. Although their struc-
ture is fairly simplistic, SOAP messages provide a powerful way to exchange serialized infor-
mation between applications.
You were also presented with information on Web services and saw how they can be created
using attributes and directives built in to the .NET platform. After they are created, Web ser-
vices can be consumed by clients through using a proxy mechanism. Because Web services can
be hit by client applications written in any language capable of serializing and deserializing
SOAP messages (or sending messages through HTTP-GET and HTTP-POST), they present a pow-
erful and useful means for clients to access and consume distributed information program-
matically.
NUMBERS AND SYMBOLS
INDEX
#FIXED value, 99
#IMPLIED value, 99
#REQUIRED value, 99
$ (dollar sign) character, 263
% (percent) character, 40
& (ampersand) character, 40
© entity, 36
  entity, 35
® entity, 36
* (asterisk), 96
* (wildcard character), 58
. (period) character, 66
.asmx extension, 431-432
.Net platform, 10-11
/ (slash) character, 54, 66
/ value, 251
/? switch, 440
/appsettingbaseurl:baseurl switch, 439
/appsettingurlkey:key switch, 439
/baseurl:baseurl switch, 439
/d[omain]:domain switch, 439
/l[anguage]:language switch, 439
/n[amespace]:namespace switch, 439
/no[backup] switch, 439
/nologo switch, 440
/o[ut]:filename switch, 440
/p[assword]:password switch, 440
/pd:domain switch, 440
/pp:password switch, 440
/protocol:protocol switch, 440
/proxy:URL switch, 440
/proxydomain:domain switch, 440
/proxypassword:password switch, 440
/proxyusername:username switch, 440
/pu:username switch, 440
/server switch
452
loading directly from SQL DataSet class, 295, 306-308, descendent-or-self axis, 56
Server into DataSet 310-314, 316-317, 319-324, description elements, 34, 113
class, 358-360 326, 328-332, 334, 336-344, description markup lan-
loading directly from SQL 346-348, 358-362 guages, 8-9
Server into DataSet property, 328 Description property, 425
XPathDocument class, dataSrc attribute, 217 descriptive tags, 7
358 datatype element, 113 detail subelement, 413
returned by date data type, 112 directives
ExecuteXmlReader( ) Date/Time extension class, EXPLICIT mode queries,
method, transforming, 282 381-384
395 dateTime data type, 112 Web services, 426-427
filling DataSets with, 309-310 dateTime.tz data type, 112 directories, virtual, 351-353
Hypertext Markup Language DCOM (Distributed disable-output-escaping
(HTML), embedding into Component Object Model), attribute, 253
eXtensible Markup 400-401 DISCO (Discovery of Web
Language (XML) elements, declarations Services), 444
41 DOCTYPE, 94-95 Discovery of Web Services
legacy, converting to eXtensible Markup Language (DISCO), 444
eXtensible Markup (XML), 21-23 displaying
Language (XML), 169, 172- External Entities, 39 DataSets as eXtensible
173 Parameter Entities, 40 Markup Language (XML),
sending from ASP.NET pages declaring 311-314, 316-317
to eXtensible Markup complexType element, 120- metadata files, 180
Language (XML) templates, 124 user entries,
388-389 entities, Document Type XMLHTTPRequest objects,
sharing between applications Definitions (DTDs), 38 211, 214-219
and components, 7 links, 82 Distributed Component
data types parameters and variables, 264 Object Model (DCOM), 400-
Simple Object Access XmlTextReader class, 153 401
Protocol (SOAP), 414, 416- default attribute, 110-111 DOCTYPE declaration, 94-95
417, 419 default namespaces, 29-31, Document Object Model
XML Data – Reduced (XML- 33 (DOM)
DR) schema, 111-113 default values, Document accessing MSXML3 through
XML schema, 116-118, Type Definition (DTD), 99 Interop, 179, 181
120-124 DefaultCredentials property, classes, System.Xml name-
DataAdapter class, 306 166 space and assembly, 182-
databases defaults, setting attributes 183
Northwind, 124-125, 127-128 to, 42 converting applications to true
updating, inserting, and delet- defining file structures, 7 eXtensible Markup
ing records, 389-394 DeleteDataSet( ) method, Language (XML) applica-
dataFld attribute, 217 337 tions, 225-227, 229-231
DataGrid WebControl, deleting database records, creating hierarchical
318-320, 334 389-394 eXtensible Markup
DataReader class, 300, 303, Depth property, 139 Language (XML) menu
306 derived data types, 117-118 application, 219, 222-223,
DataRelation class, 361 descendant axis, 56 225
Element value
461
creating structures, 183, 205, Electronic Data Interface linking, XLink, 76-77, 79-83,
207-209 (EDI), 170 85-88
described, 176-178 Extensible Hypertext Markup source, assigning to asp:Xml
how it works, 136 Language (XHTML), 43-44 Web control, 290
in-memory vs. forward-only eXtensible Markup Language Web Service Description
parsing, 178-179 (XML) Language (WSDL), 433-436
selecting nodes, XPath, 204- closing tags, 18-19 DocumentType property, 190
205 contents of, 14, 16 dollar sign ($) character, 263
troubleshooting memory prob- creating structures in DOM. See Document Object
lems, 136 browsers, 205, 207-209 Model
XmlDocument class, 189-195, creating, XmlTextWriter DOMDocument30 interface,
197-200 class, 166, 168-169, 181
XMLHTTPRequest object, 172-173 drop-down boxes, loading
211-213, 215-219 encoding types, 22-23 element data into, 214-217
XmlNamedNodeMap class, examples of, 93-95, 102, dsTables object, 314
201-202, 204 124, 258-259 dt:type attribute, 107, 111,
XmlNode class, 183-184, inferring DataSet schema 113
186-189 from, 344-345 dt:values attribute, 111
XmlNodeList class, 200-201 loading, 22, 194-196 DTD member, ValidationType
XmlNodeReader class, nesting tags, 19-20 enumerations, 161
209-210 nodes, 183 DTD. See Document Type
Document property, 290 parsing, 181 Definition
Document Type Definition proper coding of, 16-20
(DTD), 4, 7 referencing XML schema
attributes, 97-99 within, 129-130
declaring entities, 38 root elements, 17-18 E
DOCTYPE declaration, 94-95 samples of, 237-238, EDI (Electronic Data
elements, 95, 97 240-241, 246-249 Interface), 169-170, 172-173
entities, 38, 99-101 saving updates, 195 editing
external, 95 schema, 103 color attribute to <font> ele-
internal, 95 shredding, OPENXML ment, 25
notations, 101 rowset provider, database records, 389-394
reasons to use, 90-91 385-386 links, Hypertext Markup
sample eXtensible Markup templates, 236-237 Language (HTML) docu-
Language (XML) docu- transforming Extensible ments, 86
ments, 93-95 Stylesheet Language EdiToXml class, 173
validating documents with, 21, Transformations (XSLT) Electronic Data Interface
90-92 into, 259-260 (EDI), 169-170, 172-173
DocumentElement property, unique identifiers, 27-28 element data, loading into
190 validating, 20-21, 90-92, drop-down boxes, 214-217
documents 159-162 element directive, EXPLICIT
appending information to, versions of, 22 mode queries, 382
standard ASP.NET classes, Extensible Stylesheet element element, 116
46-48 Language Transformations ELEMENT keyword, 95, 97
Document Type Definition (XSLT), 260 element nodes, 154, 156
(DTD), 104-105 Hypertext Markup Language element tag, 108-109
(HTML), changing links, 86 Element value, 339
elementFormDefault attribute
462
F firewalls, Distributed
Component Object Model
false( ), 66
floor(number), 64
false( ) function, 66 (DCOM), 401 here( ), 74
Fault element, 412-414 FirstChild property, 184, 188, id( ), 60, 75
faultactor subelement, 413 190 lang(string), 66
faultcode message, 408 fixed.14.4 data type, 112 last( ), 60
faultcode subelement, 413 flag values, OPENXML local-name( ), 61
faultstring subelement, 413 rowset provider, 386 name( ), 61
favoriteCourses element, float data type, 112 namespace-uri(node), 61
121-122 floor(number) function, 64 node-set, 60-61
field element, 124-125, following axis, 56 normalize-space(string), 63
127-128 following-sibling axis, 56 not(boolean), 65
fields FOR XML AUTO keyword, number, 64-65
CustomerID, 379-380 363 origin( ), 74
mapping to eXtensible FOR XML EXPLICIT keyword, position( ), 59, 60, 66
Markup Language (XML) 383 range( ), 72-74
attributes, 339-341 FOR XML EXPLICIT keywords, round(number), 65
SOAPAction, 420 380 start-point(location-set), 74
unique, creating with XML FOR XML keywords, query- starts-with(string1,string2), 62
schema, 124-125, 127-128 ing SQL Server 2000 string, 61-64
fileName parameter, 49 through HTTP, 353-358, string-length(string), 63
files 360, 362 string-range(location-
.asmx extension, 431-432 for..next loop, 201 set,string,number,number),
ASP.NET, consuming Web foreach loop, 201, 314 73
services, 438-442, 444 foreign-relation attribute, substring(string,number,num-
comma-delimited, marking up 373 ber), 63
in eXtensible Markup form attribute, 129 substring-
Language (XML), 5-6 forward-only parsing vs. after(string1,string2), 63
defining structures of, 7 in-memory parsing, 135- substring-
eXtensible Markup Language 137, 178-179 before(string1,string2), 62
(XML), building with fragment identifiers, 71 sum(node-set), 64
ASP.NET objects, 44-45, 47- fragments, identifying with translate(string,from,to), 63-64
50 XPointer, 75-76 true( ), 65
Hypertext Markup Language from attribute, 81, 85 XPointer, 72-74
(HTML), screen scraping, functions
422 boolean, 65-66
linkbases, 86-87 ceiling(number), 65
metadata, viewing, 180 concat(string1,string2,string G
schema, 7 3, ...), 62 GenerateXml( ) method, 173
Fill( ) method, 307, 311, 314 contains(string1, string2), 62 generating
FillDropDown( ) method, 280 count( ), 252 eXtensible Markup Language
filtering nodes, 58 count(node-set), 60 (XML)
finally block, 158 end-point(location-set), 74 ASP.NET objects, 44-45,
finding paths to nodes, 54-60 Extensible Stylesheet 47-50
Language Transformations XmlTextWriter class, 168
(XSLT), 267-269, 271 proxies, Web services, 440
generic validation class, creating
466
L XmlDataDocument,
325-334
eXtensible Markup Language
(XML)
label attribute, 81, 85 XmlDocument, 197-199 capturing data, 226-227,
lang(string) function, 66 XmlNamedNodeMap, 229
last( ) function, 60 202-203 converting Electronic Data
LastChild property, 184, 190 XmlNode, property exam- Interface (EDI) to, 172-
launching IL Disassembler ples, 187 173
tool, 180 XmlNodeReader, 209-210 creating fragments,
legacy data, converting to XmlParserContext, 397- XmlTextWriter class,
eXtensible Markup 398 198-199
Language (XML), 169, 1 XpathDocument, instanti- data generated by
72-173 ating, 274-275 WriteXml( ) method,
less than (<) character, 36, 40 XmlTextReader, 142-144, 321-322
LineNumber property, 139 149-152 data generated by
LinePosition property, 139 XmlTextWriter class, WriteXmlSchema( )
linkbase keyword, 79 198-199 method, 322-324
linkbases, XLink, 86-87 XsltArgumentList, adding generating, XmlTextWriter
linking external objects to, class, 168
documents, XLink, 76-77, 283-284 inserting data into tables,
79-83, 85-88 XslTransform, 240-241, OPENXML rowset
Hypertext Markup Language 276 provider, 387
(HTML), limitations of, 77 XsltTransform, 289 loading data directly from
resources, 77, 83, 85-86 creating Simple API for XML SQL Server, 358-360
links (SAX), 147-149 loading element data,
changing, Hypertext Markup creating Simple API for XML drop-down boxes,
Language (HTML) docu- (SAX) parsers,149-152 214-217
ments, 86 DataSets mapping fields to attrib-
declaring, 82 binding to DataGrids, utes, 339-341
extended, XLink, 83, 85-86 318-320 schema, 114-115, 125-127
simple, XLink, 82 creating hierarchical rela- transforming, 281, 395
linksets, external, 86-87 tionships, 346-347 viewing DataSets as,
listings filling with data, 309-310 315-317
asp:Xml Web control, 289-290 filling with multiple tables, eXtensible Markup Language
classes 311-313 (XML) documents
DataSet, 329-334 mapping XSD schema to, creating structure of,
Date/Time extension, 282 341-343 205-208
generic validation, viewing as eXtensible embedding XPath queries
163-164 Markup Language into, 375-376
OleDbCommand, 299-300, (XML), 315-317 inferring DataSet schema
304-305 Deletegrams, 392 from, 344-345
SAXParser, in ASP.NET directives in EXPLICIT mode parsing, 181
pages, 158, 162-165, queries, 382-383 samples of, 93-95, 102,
167-168, 170-173 Document Type Definition 124, 237-238, 240-241,
SqlCommand, 301-302, (DTD) documents, 104-105 246-249, 258-259
298-299 EXPLICIT mode query, 378 schema, 103
Validator, calling from extending complex types, 123
ASP.NET pages, 165
location sets
469
Simple API for XML (SAX), creating, Extensible Stylesheet sql:field annotation, 369, 373
137, 145, 147, 149-154, 1 Language Transformations sql:id-prefix annotation, 369,
56-159 (XSLT), 278-281 373
simple data types, Simple hierarchical eXtensible sql:is-constant annotation,
Object Access Protocol Markup Language (XML) 369
(SOAP), 414, 416-417 menu, creating, 219, sql:key-fields annotation,
simple element, 84 222-223, 225 369, 373
simple link keyword, 80 sharing data between, 7 sql:limit-field annotation,
simple links, XLink, 82 source documents, assigning 370
Simple Object Access to asp:Xml Web control, sql:limit-value annotation,
Protocol (SOAP) 290 370
Body element, 410-414 sp xml removedocument sql:overflow-field annota-
described, 400, 402, 404 stored procedure, 385 tion, 370
encoding, 414, 416-417, 419 spaces,   entity, 36 sql:relation annotation, 369,
Envelope element, 407-408 sparse arrays, 417 373
Header element, 408, 410 specifying parameters, sql:relationship annotation,
HTTP headers, 419, 421 eXtensible Markup 370, 373
retrieving customer orders, Language (XML) templates, sql:target-namespace anno-
444-450 365-366 tation, 370
structure of, 404, 406-407 SQL Managed Provider sql:url-encode annotation,
Web services, 421-432, Command class, 298-303, 370
434-442, 444 305-306 sql:use-cdata annotation, 370
SimpleContent value, 339 described, 296 SqlCommand class, 298-299,
simpleType element, 118, 120 SqlConnect class, 310-311, 301-302
singletons, 71 313-314, 317 SqlConnect class, 310-311,
Skip( ) method, 142 SqlConnection class, 296-297 313-314, 317
slash (/) character, 54, 66 SqlDataAdapter class, 306- SqlConnection class, 296-297
sNodeName argument, 231 307 SqlConnection object, 314
sNodeText argument, 231 SQL Server SqlDataAdapter class, 306-
soap namespace, 408 EXPLICIT mode queries, 307
SOAP. See Simple Object 377-384 SqlDataAdapter object, 314,
Access Protocol eXtensible Markup Language 337
SOAPAction field, 420 (XLM) features in, 350 SqlDataReader class, 300
Social Security Numbers, OPENXML rowset provider, SqlDbType property, 302
eXtensible Markup 385-389 src attribute, 7
Language (XML) docu- querying stack object, 154
ments, 28 through HTTP with tem- standalone keywords, 23
software plates, XPath, and XDR standard ASP.NET classes,
building, ASP.NET objects, schema, 368-370, appending information to
44-45, 47-50 372-374, 376 documents, 46-48
converting from ASP to with HTTP, 351-352, Standard Entities, 36-37
ASP.NET, 180-181 354-358, 360, 362-365, Standard Generalized
converting to true eXtensible 367-368 Markup Language
Markup Language (XML) system tables, 4 (SGML), 4
applications, 225-227, Updategrams, 389-394 start-point(location-set)
229-231 using ADO.NET with, function, 74
394-396, 398
text modes
479