Using Schemas in .NET

only for RuBoard

Now that you have gone through the basics of XML Schemas, you're ready to work with them. You are going to work with schemas by using the xsd.exe tool, using the System.Xml namespace in .NET, and by using the schema designer in Visual Studio .NET.

xsd.exe

The .NET Framework SDK comes with several tools that make working with XML technologies simpler. One such tool is xsd.exe . This tool makes it easy to work with a specific XSD Schema by creating a class hierarchy that matches the schema, which allows you to work with the document as a class structure programmatically rather than as an XML document. You can then serialize the class structure to an XML document. The xsd.exe utility is covered in Chapter 4, and serialization will be covered in depth in Chapter 10.

Look at an example that uses the xsd.exe utility. Save the purchase order schema from Listing 2.5 as sales.xsd because you will use xsd.exe to generate a set of classes based on this schema. Then you are able to use those classes to populate data and create a valid XML file using serialization. Open the Visual Studio .NET command prompt (located at Start, Programs, Microsoft Visual Studio .NET 7.0,Visual Studio .NET Tools). I changed the directory path to the location of the XSD file and entered the following command-line prompt:

 xsd sales.xsd /classes /language:vb /outputdir:c:\temp 

This prompt yielded a class file with a .vb extension in the c:\temp directory

named sales.vb . Listing 2.6 shows the generated code.

Listing 2.6 Generated Code from xsd.exe
 '---------------------------------------------------------------------------- ' <autogenerated>  '     This code was generated by a tool.  '     Runtime Version: 1.0.2914.16  '  '     Changes to this file may cause incorrect behavior and will be lost if  '     the code is regenerated.  ' </autogenerated>  '---------------------------------------------------------------------------- Option Strict Off  Option Explicit On  Imports System.Xml.Serialization  '  'This source code was auto-generated by xsd, Version=1.0.2914.16.  '  <System.Xml.Serialization.XmlRootAttribute([Namespace]:="http://localhost/chapters/03/ graphics/ccc.gif xmlschemas/schemas/sales/Sales.xsd", IsNullable:=false)>  Public Class PURCHASEORDER      <System.Xml.Serialization.XmlElementAttribute("CUSTOMER", IsNullable:=false)>  _      Public CUSTOMER() As customerType      <System.Xml.Serialization.XmlArrayAttribute(IsNullable:=false),       System.Xml.Serialization.XmlArrayItemAttribute("ITEM", GetType(itemType), graphics/ccc.gif IsNullable:=false)>  _      Public ORDER() As itemType  End Class  Public Class customerType      <System.Xml.Serialization.XmlElementAttribute(IsNullable:=false)>      Public NAME As String      <System.Xml.Serialization.XmlElementAttribute(IsNullable:=false)>      Public PHONE As String      <System.Xml.Serialization.XmlElementAttribute(IsNullable:=false)>      Public EMAIL As String      <System.Xml.Serialization.XmlElementAttribute(IsNullable:=false)>      Public BILLING As addressType  End Class  Public Class addressType      <System.Xml.Serialization.XmlElementAttribute(IsNullable:=false)>      Public Name As String      <System.Xml.Serialization.XmlElementAttribute(IsNullable:=false)>      Public Street1 As String      <System.Xml.Serialization.XmlElementAttribute(IsNullable:=false)>      Public Street2 As String      <System.Xml.Serialization.XmlElementAttribute(IsNullable:=false)>      Public City As String      <System.Xml.Serialization.XmlElementAttribute(IsNullable:=false)>      Public State As String      <System.Xml.Serialization.XmlElementAttribute(DataType:="integer",  IsNullable:=false)>      Public Zip As String  End Class  <System.Xml.Serialization.XmlTypeAttribute([Namespace]:="http://localhost/chapters/03/ graphics/ccc.gif xmlschemas/schemas/sales/Sales.xsd")>  Public Class itemType      <System.Xml.Serialization.XmlElementAttribute(IsNullable:=false)>      Public ITEMNAME As String      <System.Xml.Serialization.XmlElementAttribute(IsNullable:=false)>      Public DESCRIPTION As String      <System.Xml.Serialization.XmlElementAttribute(IsNullable:=false)>      Public SIZE As String      Public PRICE As Decimal      <System.Xml.Serialization.XmlIgnoreAttribute()>      Public PRICESpecified As Boolean      <System.Xml.Serialization.XmlAttributeAttribute()>      Public name As String      <System.Xml.Serialization.XmlAttributeAttribute()>      Public value As String      <System.Xml.Serialization.XmlAttributeAttribute()>      Public description As String      <System.Xml.Serialization.XmlAttributeAttribute()>      Public color As colorEnum      <System.Xml.Serialization.XmlIgnoreAttribute()>      Public colorSpecified As Boolean  End Class  Public Enum colorEnum      Blue      Red      Black      Green  End Enum 

As with most code generators, shortcomings exist in the code. Naming things in XML documents usually differs from naming items in code, but the utility uses the names in your schema document for local variable names . The .NET Framework Design Guidelines specifies pascal casing for public properties and fields, but the generated code uses capital letters for elements and lowercase letters for attributes. I also prefer to use property procedures rather than direct field access in classes. However, the generated code serves as a good baseline for developing a set of classes to work with the XML document. Be assured that an XML document generated from this structure is generated in the proper format.

One real shortcoming of this utility is the fact that the facets for your simple types are not preserved. For example, the regular expression that you specified for emailType is not represented within the code and there is no restriction within your class hierarchy to enforce the constraints set out. Of course, this is a function of the XML schema document, but it would have been more impressive if the object hierarchy could enforce the constraints specified in the XML Schema document.

Popular Utility Development Opportunity

Although the implementation of constraining facets is not represented within generated classes, this shortcoming presents some interesting possibilities for developing your own utility that preserves the constraining facet behaviors using the CodeDom classes and System.Xml. Serialization.XmlCodeExporter to generate the code.

Another shortcoming is how you name the elements within a schema. XSD Schemas maintain different name tables for each schema component. This means that an attribute can have the same name as an element within the same class. Notice that in the itemType class in the preceding code, there are two different declarations for description : The capitalized version, DESCRIPTION , which represents the element (I use uppercase letters for elements by convention within this document), and the lowercase version that represents an attribute called description . Remember that XML is case-sensitive. But, because Visual Basic is not case-sensitive, the preceding generated code would not compile because the name description is declared twice. This would not be a limitation in C# because C# is case-sensitive. Either way, you will probably opt not to stick with the default naming within the document.

To rename the internal variables in your code but still preserve the generated XML Schema, the designers of .NET included attributes to describe exactly what the serialized XML should look like. To do this, simply rename the internal variable for the lowercase description attribute and specify the name of the attribute in the serialization attribute as follows :

 <System.Xml.Serialization.XmlAttributeAttribute("description")>      Public AttribDescription As String 

After that is corrected, you can work with the generated class hierarchy. Listing 2.7 is a sample code listing for working with the new class hierarchy created in Listing 2.6. You will create a simple ASP page that will populate the typed DataSet with data and serialize the DataSet to XML. The ASP layout code is a simple interface that consists of labels and textboxes for the various properties. There are also one drop-down list box and three buttons . The UI code is unimportant, so you need to focus on the code-behind Visual Basic code.

The first section simply declares the UI elements to be used in the codebehind class.

Listing 2.7 A Simple ASP.NET Web Application Referencing a Typed DataSet
 Imports System  Imports System.Xml  Imports System.Xml.Schema  Public Class sales      Inherits System.Web.UI.Page      Protected WithEvents Label1 As System.Web.UI.WebControls.Label      Protected WithEvents Label2 As System.Web.UI.WebControls.Label      Protected WithEvents txtPhone As System.Web.UI.WebControls.TextBox      Protected WithEvents Label3 As System.Web.UI.WebControls.Label      Protected WithEvents txtEmail As System.Web.UI.WebControls.TextBox      Protected WithEvents Label4 As System.Web.UI.WebControls.Label      Protected WithEvents txtBillToName As System.Web.UI.WebControls.TextBox      Protected WithEvents Label5 As System.Web.UI.WebControls.Label      Protected WithEvents txtStreet1 As System.Web.UI.WebControls.TextBox      Protected WithEvents Label6 As System.Web.UI.WebControls.Label      Protected WithEvents txtStreet2 As System.Web.UI.WebControls.TextBox      Protected WithEvents Label7 As System.Web.UI.WebControls.Label      Protected WithEvents txtCity As System.Web.UI.WebControls.TextBox      Protected WithEvents Label8 As System.Web.UI.WebControls.Label      Protected WithEvents txtState As System.Web.UI.WebControls.TextBox      Protected WithEvents Label9 As System.Web.UI.WebControls.Label      Protected WithEvents txtZip As System.Web.UI.WebControls.TextBox      Protected WithEvents Label10 As System.Web.UI.WebControls.Label      Protected WithEvents txtItemName As System.Web.UI.WebControls.TextBox      Protected WithEvents Label11 As System.Web.UI.WebControls.Label      Protected WithEvents txtDescription As System.Web.UI.WebControls.TextBox      Protected WithEvents Label12 As System.Web.UI.WebControls.Label      Protected WithEvents txtSize As System.Web.UI.WebControls.TextBox      Protected WithEvents Label14 As System.Web.UI.WebControls.Label      Protected WithEvents txtAttribName As System.Web.UI.WebControls.TextBox      Protected WithEvents Label15 As System.Web.UI.WebControls.Label      Protected WithEvents txtAttribValue As System.Web.UI.WebControls.TextBox      Protected WithEvents Label16 As System.Web.UI.WebControls.Label      Protected WithEvents txtAttribDescription As System.Web.UI.WebControls.TextBox      Protected WithEvents Label17 As System.Web.UI.WebControls.Label      Protected WithEvents DropDownList1 As System.Web.UI.WebControls.DropDownList      Protected WithEvents Button1 As System.Web.UI.WebControls.Button      Protected WithEvents Button2 As System.Web.UI.WebControls.Button      Protected WithEvents Button3 As System.Web.UI.WebControls.Button      Protected WithEvents txtName As System.Web.UI.WebControls.TextBox  #Region " Web Form Designer Generated Code "      'This call is required by the Web Form Designer.      <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()      End Sub      Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) graphics/ccc.gif Handles MyBase.Init          'CODEGEN: This method call is required by the Web Form Designer          'Do not modify it using the code editor.          InitializeComponent()      End Sub  #End Region 

When the page loads, you might want to add sample items to the drop-down list to work with your generated class hierarchy:

 Private Sub Page_Load(ByVal sender As System.Object, ByVal e As  System.EventArgs) Handles MyBase.Load          If Not IsPostBack then          DropDownList1.Items.Add("0 - Blue")          DropDownList1.Items.Add("1 - Red")          DropDownList1.Items.Add("2 - Black")          DropDownList1.Items.Add("3 - Green")       End if      End Sub 

The code for the Click event of the Button1 object populates the hierarchy with the contents of the form and serializes the hierarchy of objects to an XML file. You must look at the individual steps to achieve this. Begin by declaring the object references and creating a new object instance for each reference:

 Private Sub Serialize(ByVal sender As System.Object, ByVal e As  System.EventArgs) Handles _  Button1.Click          Dim po As XMLSchemas.PURCHASEORDER = New PURCHASEORDER()          Dim cust As XMLSchemas.customerType = New customerType()          Dim item As XMLSchemas.itemType = New XMLSchemas.itemType()          Dim billing As XMLSchemas.addressType = New XMLSchemas.addressType() 

So far, you have only set up the object references by using the types exposed by generated classes. To create instances of the objects, you need to create an object array to hold the object references. The generated classes support a CreateInstance method to create an instance of a class contained as a child. You can use the CreateInstance method to create instances of classes based on their type:

 'Create a 1-element array of customerType objects          po.CUSTOMER = po.CUSTOMER.CreateInstance(cust.GetType, 1)          'Create a 1-element array of itemType objects          po.ORDER = po.ORDER.CreateInstance(item.GetType, 1) 

The first parameter to the CreateInstance method is a System.Type argument. The cust object is an object reference, not a type, so you need to get the type that it represents by using the GetType method. The second parameter to CreateInstance simply states how many elements the array will contain. For simplicity, suppose that it will hold one object.

After the containing arrays are instantiated and associated with the parent objects, you can populate the array contents, as shown here:

 With cust      .EMAIL = Me.txtEmail.Text      .NAME = Me.txtBillToName.Text              .PHONE = Me.txtPhone.Text          End With          'Set the first element in the customer array to the populated  customerType object          po.CUSTOMER(0) = cust          With billing              .Name = Me.txtName.Text              .Street1 = Me.txtStreet1.Text              .City = Me.txtCity.Text              .State = Me.txtState.Text              .Zip = Me.txtZip.Text          End With          'Set the customer's billing address property          cust.BILLING = billing          With item              .color = DropDownList1.SelectedIndex              'The color is always specified              .colorSpecified = True              .ITEMNAME = "Big Screen TV"              'We did not include the price element on the UI              .PRICESpecified = False              .SIZE = Me.txtSize.Text              .name = Me.txtAttribName.Text              .value = Me.txtAttribValue.Text          End With          po.ORDER(0) = item 

After the object hierarchy is completely populated, serialize the contents of the entire hierarchy to a file. We hard-coded the path to the XML file for brevity, but you probably want to use some other means of dynamically retrieving the file's name and/or contents. Again, use the GetType method to retrieve the po object's type so that the XmlSerializer knows what to serialize. The Serialize method is then called to serialize the po object to the specified XML file, as shown here:

 'Serialize the purchase order to an XML file.          Dim serializer As System.Xml.Serialization.XmlSerializer          Dim file As System.IO.FileStream = New  System.IO.FileStream("c:\temp\xmlfromclass.xml", _  IO.FileMode.OpenOrCreate)          serializer = New System.Xml.Serialization.XmlSerializer(po.GetType)          serializer.Serialize(file, po)          file.Close()      End Sub 

To rehydrate the object based on the persisted XML file, do the reverse of the serialization process. Instantiate a po object to be deserialized and then populate the web form based on the contents of the hierarchy:

 Private Sub Deserialize(ByVal sender As System.Object, ByVal e As  System.EventArgs) Handles Button2.Click          Dim po As XMLSchemas.PURCHASEORDER = New XMLSchemas.PURCHASEORDER()          Dim serializer As System.Xml.Serialization.XmlSerializer          Dim file As System.IO.FileStream = New  System.IO.FileStream("c:\temp\xmlfromclass.xml", _  IO.FileMode.Open)          'Open the XML file and re-hydrate the purchase order object          serializer = New System.Xml.Serialization.XmlSerializer(po.GetType)          po = serializer.Deserialize(file)          file.Close()          'Populate the web form elements' text property          With po.CUSTOMER(0)              txtName.Text = .NAME              txtEmail.Text = .EMAIL              txtPhone.Text = .PHONE          End With          With po.CUSTOMER(0).BILLING              txtStreet1.Text = .Street1              txtStreet2.Text = .Street2              txtCity.Text = .City              txtState.Text = .State              txtZip.Text = .Zip          End With          With po.ORDER(0)              txtItemName.Text = .ITEMNAME              txtDescription.Text = .DESCRIPTION              txtSize.Text = .SIZE              txtAttribName.Text = .name              txtAttribValue.Text = .value              DropDownList1.SelectedIndex = .color          End With      End Sub  End Class 

This example shows how easy it is to work with the generated class files and how quick it is to generate the classes from an XSD Schema. This section also gives a brief introduction to serialization.

Schemas in ADO.NET

You have seen that new tools in Visual Studio .NET help generate XML Schemas, so now it's time to look at how XSD Schemas are interwoven into .NET. XSD Schemas are tightly integrated into ADO.NET. In fact, the DataSet in ADO.NET is structured around XSD Schemas to represent the structure of the data. The DataSet class provides the capability to read schemas, validate the contained data against schemas, and to infer schemas based on the data they contain. Because a DataSet is a view of data, its structure can be represented in an XML Schema. Working with schemas and DataSets is a key part of the ADO.NET strategy because the underlying data can easily interoperate even with non-.NET clients . Although you do not have to know much about XML or XSD Schemas to work with ADO.NET, a firm understanding of XML and schemas help you to gain mastery of .NET development.

Generating Schemas from Queries

One of the easiest operations with a DataSet is to generate a schema based on the DataSet's contents. In the following code, you read a table from a database, use the WriteXMLSchema method to write the schema to an interim stream, and return the contents of the stream to the calling function:

 Private Function GetSchemaFromQuery(ByVal query As String, _            ByVal connectionString As String) As String      Dim connection As System.Data.SqlClient.SqlConnection      Dim adapter As System.Data.SqlClient.SqlDataAdapter      Dim mydataSet As System.Data.DataSet = New Data.DataSet()      Dim memStream As System.IO.MemoryStream = New System.IO.MemoryStream()      Dim writer As System.Xml.XmlTextWriter      Dim reader As System.IO.StreamReader      'Get the data from the database      connection = New SqlClient.SqlConnection(connectionString)      adapter = New SqlClient.SqlDataAdapter(query, connection)      adapter.Fill(mydataSet)      'Release database resources      adapter.Dispose()      connection.Close()      connection.Dispose()      'Write the schema to the memory stream      writer = New System.Xml.XmlTextWriter(memStream,  System.Text.Encoding.UTF8)  mydataSet.WriteXmlSchema(writer)  'reset the position of the stream to the beginning      memStream.Position = 0      'Read the data from the memory stream      reader = New System.IO.StreamReader(memStream)      Return reader.ReadToEnd      'Release resources      reader.Close()      memStream.Close()      mydataSet.Dispose()  End Function 

ADO.NET provides several methods for creating a schema structure from the XML data in a DataSet. The InferXMLSchema method of the DataSet object creates a schema that's based on the contents of the XML data and allows you to specify namespaces to ignore. Similarly, the ReadXML and ReadXMLSchema both perform similar functions.

Typed DataSets in ADO.NET

One key feature of ADO.NET is its capability to work with a strongly typed DataSet. A strongly typed DataSet is a class that inherits from the DataSet class and uses information from an XML Schema to generate a new class as a set of first-class properties and methods (MSDN, "Introduction to DataSets"). As you saw earlier with the xsd.exe utility, the structure expressed in an XSD Schema file can map to the hierarchy of a DataSet class as a collection of tables. Figure 2.3 gives you a view of the hierarchical structure of a DataSet and the classes it contains.

Figure 2.3. The hierarchical structure of a DataSet.
graphics/02fig03.gif

DataSets are covered in more detail in Chapter 8.

Exploring System.Xml.Schema

With all the integration of schemas into .NET and Visual Studio .NET, no wonder there is a mature set of classes in the .NET Framework to work with schemas. Although you can work with schemas using the DataSet class, the rest of this chapter focuses on working with the System.Xml.Schema classes and performing validation against schema documents.

The DataSet class enables you to work with a schema document as a set of tables, but you might find that you need to programmatically access a schema and determine its exact structure. The classes in the System.Xml.Schema namespace provide this functionality without writing custom parsing routines to parse the document simply as a raw XML document.

The designers of the framework designed a close representation of the W3C recommendation model. Class naming is relatively consistent with the recommendation model (although you need to pay attention to terms and definitions, such as particle, content, and model).

System.Xml.Schema.XmlSchema

The root node of a schema document is the schema element. The class framework provides this node through the XmlSchema class. All schema elements are children of the XmlSchema class, so they are at the top of the hierarchy. You can use the System.Xml.Schema classes to generate the following schema document shown in Listing 2.8. The output first is provided first so it's easier to reference the code that is used to create the schema document .

Listing 2.8 Address Book Schema
 <?xml version="1.0" encoding="utf-8" ?>  <xsd:schema id="addressbook" targetNamespace="urn:schemas-vbdna net:addressbook" elementFormDefault="qualified" xmlns:tns="urn:schemas-vbdna net:addressbook" xmlns:xsd="http://www.w3.org/2001/XMLSchema">       <xsd:complexType name="addressType">            <xsd:sequence minOccurs="1" maxOccurs="1">                 <xsd:sequence>                     <xsd:element name="street1" minOccurs="1" maxOccurs="1" graphics/ccc.gif type="xsd:string" />                     <xsd:element name="street2" minOccurs="0" maxOccurs="1" graphics/ccc.gif type="xsd:string" />                     <xsd:element name="city" minOccurs="1" maxOccurs="1" type="xsd:string" graphics/ccc.gif />                     <xsd:element name="state" minOccurs="1" maxOccurs="1" graphics/ccc.gif type="xsd:string" />                     <xsd:element name="zip" minOccurs="1" maxOccurs="1" type="tns:zipType" graphics/ccc.gif />                 </xsd:sequence>            </xsd:sequence>       </xsd:complexType>       <xsd:simpleType name="zipType">            <xsd:restriction base="xsd:string">                <xsd:pattern value="\d{5}(-\d{4})?" />            </xsd:restriction>       </xsd:simpleType>       <xsd:simpleType name="emailType">            <xsd:restriction base="xsd:string">                 <xsd:pattern value="\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" />            </xsd:restriction>       </xsd:simpleType>       <xsd:element name="addressBook">            <xsd:complexType>                 <xsd:sequence minOccurs="0" maxOccurs="unbounded">                      <xsd:element name="entry">                            <xsd:complexType>                               <xsd:sequence>                                   <xsd:element name="name" type="xsd:string" />                                     <xsd:element name="address" type="tns:addressType" />                                     <xsd:element name="email" type="tns:emailType" graphics/ccc.gif minOccurs="0" maxOccurs="unbounded" />                                </xsd:sequence>                           </xsd:complexType>                      </xsd:element>                 </xsd:sequence>            </xsd:complexType>       </xsd:element>  </xsd:schema> 

Begin by defining the XmlSchema class object and defining the namespace that will be used as the targetNamespace in Listing 2.9. The XmlSchema class object contains an Items collection that adds items as children of the schema node.

Listing 2.9 Using the System.Xml.Schema Namespace
 using System;  using System.Xml;  using System.Xml.Schema;  namespace XmlSchemaCS  {      public class CustomerSchemaFunctions       {           public const string W3C_SCHEMA_NS ="http://www.w3.org/2001/XMLSchema";            private  Boolean m_IsValid = false; ///this is a comment for the boolean graphics/ccc.gif variable            private string m_targetNameSpace = "";            //Only ctor, with parameter            public CustomerSchemaFunctions(string nameSpace)            {                m_targetNameSpace = nameSpace;            }       .       .       .            public void WriteSchemaXml(string xsdFile)                 /// <summary>function to write the schema as xml</summary>            {               ///Create the schema root node with the targetNamespace                XmlSchema schema = new XmlSchema();                schema.TargetNamespace=m_targetNameSpace;                schema.AttributeFormDefault = XmlSchemaForm.Unqualified;                schema.ElementFormDefault = XmlSchemaForm.Qualified; ;                XmlQualifiedName schemaString = new XmlQualifiedName("string", graphics/ccc.gif W3C_SCHEMA_NS);                schema.Items.Add(CreateZipType());                schema.Items.Add(CreateEmailType());                schema.Items.Add(CreateAddressType());                schema.Items.Add(CreateEntryType());  .  .  .            }  } 

The code in Listing 2.9 is not a complete working example yet, but shows the beginnings of the complete sample (shown in Listing 2.10).

Looking at the XML Schema document, the children of the schema node are the addressBook element, the zipType , emailType simple types, and the addressType complex type. These will be added as children of the schema node. The XmlSchema class object contains an Items collection that adds items as children of the schema node.

Working with the XmlSchema classes require you to think backwards . Instead of creating a simpleType , adding a restriction, then adding the facets, you must work in reverse. Create the facet, create the restriction, add the facet to the facets collection of the restriction, and add the restriction to the simpleType . The simpleType class does not expose an Items collection like the XmlSchema object. Instead, you specify the child elements of the xsd:simpleType element by using the XmlSchemaSimpleType.Content property. The following code creates the root schema node and a simple type emailType . I split the creation of the simpleType and the schema element to better show the scope of each method:

 private XmlSchemaSimpleType CreateEmailType()  {      XmlSchemaSimpleTypeRestriction restriction = new XmlSchemaSimpleTypeRestriction ();       restriction.BaseTypeName = new System.Xml.XmlQualifiedName("string",W3C_SCHEMA_NS);       XmlSchemaPatternFacet pattern = new XmlSchemaPatternFacet();       pattern.Value ="\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*";       restriction.Facets.Add (pattern);       XmlSchemaSimpleType emailType = new XmlSchemaSimpleType();       emailType.Name = "emailType";  emailType.Content = restriction;  return(emailType);  } 

Notice that the WriteSchemaXml method in Listing 2.9 calls schema.Items.Add() . This is how the simpleType is actually appended to the schema. Simply creating an XmlSchemaObject object does not append it to the schema object; it must be explicitly added through the Add method. Failure to do this results in the type missing from the generated schema.

Creating a complexType is similar to creating an XmlSchemaSimpleType . To create a complex type, use the Schema.XmlSchemaComplexType object, as shown in Listing 2.10. As with the XmlSchemaSimpleType object, the XmlSchemaComplexType does not support an Items collection. To specify child content, use the Particle property to specify an all, choice, or sequence group particle.

Listing 2.10 Using Particles and SOM
 private XmlSchemaElement  GenElement  ( string name, int minOccurs,  int maxOccurs)            {                XmlSchemaElement returnElement = new XmlSchemaElement();                 returnElement.Name = name;                 returnElement.MinOccurs = minOccurs;                 returnElement.MaxOccurs = maxOccurs;                 return (returnElement);            }            private XmlSchemaElement  GenElement  (string name, int minOccurs,                 int maxOccurs, System.Xml.XmlQualifiedName qualifiedName)            {                XmlSchemaElement element = new XmlSchemaElement();                 element.Name = name;                 element.MinOccurs = minOccurs;                 element.MaxOccurs = maxOccurs;                 element.SchemaTypeName = qualifiedName;                 return (element);            }            private XmlSchemaComplexType CreateAddressType()            {  System.Xml.XmlQualifiedName schemaString = new   System.Xml.XmlQualifiedName("string",W3C_SCHEMA_NS);  XmlSchemaSequence seq = new XmlSchemaSequence();                 seq.MinOccurs=1;                 seq.MaxOccurs=1;                 seq.Items.Add (GenElement("street1",1,1,schemaString));                 seq.Items.Add (GenElement("street2",0,1,schemaString));                 seq.Items.Add (GenElement("city",1,1,schemaString));                 seq.Items.Add (GenElement("state",1,1,schemaString));                 seq.Items.Add (GenElement("zip",1,1,new  System.Xml.XmlQualifiedName("zipType",m_targetNameSpace)));                 XmlSchemaComplexType addressType = new XmlSchemaComplexType();                 addressType.Name = "addressType";                 addressType.Particle = seq;                 return(addressType);            } 

Rather than typing the code to create single elements, I created an overloaded helper function, GenElement (shown in Listing 2.10 as a shaded line), to facilitate the creation of elements. The overloaded version of this helper function accepts a parameter called qualifiedName : This is the QName that you can use to associate the type name ( zipType ) with your namespace ( urn:schemas- xmlandasp-net:framework ). You also created a local variable in the custom Create AddressType function called schemaString that declares the QName for the built-in string type ( xsd:string ).

After you're done creating each type, append it to the schema.Items collection by using the Add method. After all the types and your root element are appended to the schema, you can then retrieve the XML representation of the schema. I used an XmlTextWriter object to write the schema to a file, although you might want to store the schema as a string or keep it in memory.

Before writing the file, you need to call the Compile method. The Compile method, as shown in Listing 2.11, compiles the contents of the Schema Object Model (SOM) so that the schema can then be read or written. When you write the file, you must set up an event handler to capture errors thrown if a problem with the schema occurs.

A separate code listing for Visual Basic .NET isn't included here because the code is so similar between the two languages. The code is available on the book's website, however, which is located at www.xmlandasp.net. The main differences between the Visual Basic .NET version and the C# version is how the regular expressions look. C# requires a delimiter for \ characters , while Visual Basic .NET does not. So, the following line in C#:

 pattern.Value ="\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*"; 

is changed to the following line in Visual Basic .NET:

 pattern.Value = "\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" 

Listing 2.11 contains the complete code listing, available for download as listing2-11.cs , for generating the schema as previously described.

Listing 2.11 The Complete SOM Example
 using System;  using System.Xml;  using System.Xml.Schema;  namespace XmlSchemaCS  {      public class CustomerSchemaFunctions       {           public const string W3C_SCHEMA_NS ="http://www.w3.org/2001/XMLSchema";            private Boolean m_IsValid = false; ///this is a comment for the boolean graphics/ccc.gif variable            private string m_targetNameSpace = "";            public CustomerSchemaFunctions(string nameSpace)            {                m_targetNameSpace = nameSpace;            }            public void WriteSchemaXml(string xsdFile)                 /// <summary>function to write the schema as xml</summary>            {                ///Create the schema root node with the targetNamespace                 XmlSchema schema = new XmlSchema();                 schema.TargetNamespace=m_targetNameSpace;                 schema.AttributeFormDefault = XmlSchemaForm.Unqualified;                 schema.ElementFormDefault = XmlSchemaForm.Qualified; ;                 XmlQualifiedName schemaString = new XmlQualifiedName("string", graphics/ccc.gif W3C_SCHEMA_NS);                 schema.Items.Add(CreateZipType());                 schema.Items.Add(CreateEmailType());                 schema.Items.Add(CreateAddressType());                 schema.Items.Add(CreateEntryType());                 XmlSchemaSequence seq = new XmlSchemaSequence();                 seq.MinOccurs=0;                 seq.MaxOccurs=1000;                 seq.Items.Add(GenElement("entry",1,1000,new XmlQualifiedName("entryType", graphics/ccc.gif m_targetNameSpace)));                 XmlSchemaComplexType complexType = new XmlSchemaComplexType();                 complexType.Particle = seq;                 XmlSchemaElement addressBook = new XmlSchemaElement();                 addressBook.Name = "addressBook";                 addressBook.SchemaType = complexType;                 schema.Items.Add (addressBook);                 XmlTextWriter writer = new XmlTextWriter (xsdFile, graphics/ccc.gif System.Text.Encoding.UTF8);                 schema.Compile(new ValidationEventHandler(ValidationCallback));                 schema.Write(writer);                 writer.Close();             }            private XmlSchemaElement GenElement( string name, int minOccurs, int maxOccurs)            {                XmlSchemaElement returnElement = new XmlSchemaElement();                 returnElement.Name = name;                 returnElement.MinOccurs = minOccurs;                 returnElement.MaxOccurs = maxOccurs;                 return (returnElement);            }            private XmlSchemaElement GenElement(string name, int minOccurs,                 int maxOccurs, System.Xml.XmlQualifiedName qualifiedName)            {                XmlSchemaElement element = new XmlSchemaElement();                 element.Name = name;                 element.MinOccurs = minOccurs;                 element.MaxOccurs = maxOccurs;                 element.SchemaTypeName = qualifiedName;                 return (element);            }            private XmlSchemaSimpleType CreateZipType()            {                XmlSchemaSimpleTypeRestriction restriction = new graphics/ccc.gif XmlSchemaSimpleTypeRestriction ();                 restriction.BaseTypeName = new System.Xml.XmlQualifiedName("string", graphics/ccc.gif W3C_SCHEMA_NS);                 XmlSchemaPatternFacet pattern = new XmlSchemaPatternFacet();                 pattern.Value ="\d{5}(-\d{4})?";                 restriction.Facets.Add (pattern);                 XmlSchemaSimpleType zipType = new XmlSchemaSimpleType();                 zipType.Name = "zipType";                 zipType.Content = restriction;                 return(zipType);            }            private XmlSchemaSimpleType CreateEmailType()            {                XmlSchemaSimpleTypeRestriction restriction = new graphics/ccc.gif XmlSchemaSimpleTypeRestriction ();                 restriction.BaseTypeName = new System.Xml.XmlQualifiedName("string", graphics/ccc.gif W3C_SCHEMA_NS);                 XmlSchemaPatternFacet pattern = new XmlSchemaPatternFacet();                 pattern.Value ="\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*";                 restriction.Facets.Add (pattern);                 XmlSchemaSimpleType emailType = new XmlSchemaSimpleType();                 emailType.Name = "emailType";                 emailType.Content = restriction;                 return(emailType);            }            private XmlSchemaComplexType CreateAddressType()            {                System.Xml.XmlQualifiedName schemaString = new graphics/ccc.gif System.Xml.XmlQualifiedName("string",W3C_SCHEMA_NS);                 XmlSchemaSequence seq = new XmlSchemaSequence();                 seq.MinOccurs=1;                 seq.MaxOccurs=1;                 seq.Items.Add (GenElement("street1",1,1,schemaString));                 seq.Items.Add (GenElement("street2",0,1,schemaString));                 seq.Items.Add (GenElement("city",1,1,schemaString));                 seq.Items.Add (GenElement("state",1,1,schemaString));                 seq.Items.Add (GenElement("zip",1,1,new graphics/ccc.gif System.Xml.XmlQualifiedName("zipType",m_targetNameSpace)));                 XmlSchemaComplexType addressType = new XmlSchemaComplexType();                 addressType.Name = "addressType";                 addressType.Particle = seq;                 return(addressType);            }            private XmlSchemaComplexType CreateEntryType()            {                System.Xml.XmlQualifiedName schemaString = new graphics/ccc.gif System.Xml.XmlQualifiedName("string",W3C_SCHEMA_NS);                 XmlSchemaSequence seq = new XmlSchemaSequence();                 seq.MinOccurs = 1;                 seq.MaxOccurs = 1;                 seq.Items.Add (GenElement("name",1,1,schemaString));                 seq.Items.Add (GenElement("address",1,1, new graphics/ccc.gif System.Xml.XmlQualifiedName("addressType",m_targetNameSpace)));                 seq.Items.Add (GenElement("email",0,4, new graphics/ccc.gif System.Xml.XmlQualifiedName("emailType",m_targetNameSpace)));                 XmlSchemaComplexType entryType = new XmlSchemaComplexType();                 entryType.Name = "entryType";                 entryType.Particle = seq;                 return(entryType);            }       }  } 
System.Xml.Schema.XmlSchemaCollection

Because there is no Read method on an XmlSchema object, how does someone access a schema programmatically using System.Xml.Schema classes? The answer is through the System.Xml.Schema.XmlSchemaCollection object. This object is a collection of schemas that you load from a URL source or an XmlTextReader object. You can add multiple schemas to the collection and then work with all of them programmatically. XmlSchemaCollection is used here to read the schema file created in the previous section and to access it programmatically.

The XmlTextReader accepts a URL for one of its constructors, so the file:// moniker is being used to load the file from the file system. You can add the schema to the XmlSchemaCollection object to populate the SOM. You can retrieve a reference to the populated SOM (the XmlSchema object) by using the collection accessor foreach in C# (the same as for each in Visual Basic). After you have a reference to the XmlSchema object, you can access its properties and methods, as follows:

 public void ReadSchemaXml(string xsdFile)            {                //Read the schema from file using the file:// moniker.                 System.Xml.XmlTextReader  reader = new System.Xml.XmlTextReader(xsdFile);                 XmlSchemaCollection schemas = new XmlSchemaCollection();                 //Add the schema to the schemas collection                 schemas.Add(m_targetNameSpace,reader);                 foreach (XmlSchema  schema in schemas)                 {                     //We now have the schema object                      try                      {                           System.Diagnostics.Debug.WriteLine (schema.TargetNamespace);  .  .  .                      }                      catch(System.Exception e)                      {                          System.Diagnostics.Debug.WriteLine(e.ToString());                      }                 }  } 

This is not a complete listing, however; you still have to fill in some gaps. After you have a reference to the schema object, you can access its properties and child nodes the same way that you added them. You can use the for each syntax to cycle through the Items collection to look for child elements of the schema node. Each object in the Items collection has a base type of XmlSchemaObject , so you can test each object to see what types it supports and cast to the appropriate type:

 foreach ( XmlSchemaObject element in schema.Items)  { if(element is XmlSchemaSimpleType)       {           XmlSchemaSimpleType simpleType = (XmlSchemaSimpleType)element;            XmlSchemaSimpleTypeContent content = simpleType.Content ;            if (content is XmlSchemaSimpleTypeRestriction)            {           XmlSchemaSimpleTypeRestriction restriction = (XmlSchemaSimpleTypeRestriction graphics/ccc.gif )simpleType.Content;            foreach(XmlSchemaFacet facet in restriction.Facets)            {                if (facet is XmlSchemaPatternFacet)                 {            .             .             .                 }            }       else if(element is XmlSchemaComplexType)       {      }       else             .             .             .  } 

By using the Is operator, you can cast each object to a workable type to access the object-specific properties and methods. If you are working with a simpleType object, you need to reference its Content property. If you are working with a complexType object, you need access to its Particle property.

You can expand the simple type logic to display the XML in the debug window. You use the Is operator and a foreach loop to cycle through the facets collection and display only those facets that are pattern facets (facets that use regular expressions for validation):

 foreach ( XmlSchemaObject element in schema.Items)  {      if(element is XmlSchemaSimpleType)       {         XmlSchemaSimpleType simpleType = (XmlSchemaSimpleType)element;          System.Diagnostics.Debug.WriteLine ("<xsd:simpleType name=" + simpleType.Name );          XmlSchemaSimpleTypeContent content = simpleType.Content ;          if (content is XmlSchemaSimpleTypeRestriction)          {            XmlSchemaSimpleTypeRestriction restriction = null;             restriction = (XmlSchemaSimpleTypeRestriction) simpletype.Content;             System.Diagnostics.Debug.WriteLine("<xsd:restriction base=\"" + graphics/ccc.gif restriction.BaseTypeName + "\">");             foreach(XmlSchemaFacet facet in restriction.Facets)             {                  if (facet is XmlSchemaPatternFacet)                   {            System.Diagnostics.Debug.WriteLine("<xsd:pattern value=\"" + facet.Value+ "\"/ graphics/ccc.gif >");                }             }             System.Diagnostics.Debug.WriteLine("<//xsd:restriction>");        }        System.Diagnostics.Debug.WriteLine ("<//xsd:simpleType");    }    else if(element is XmlSchemaComplexType)    {       XmlSchemaComplexType complexType = (XmlSchemaComplexType)element;        System.Diagnostics.Debug.WriteLine (complexType.Name );      }      else              System.Diagnostics.Debug.WriteLine(element.ToString());  } 

Notice that the Content property of the XmlSchemaSimpleType method returns an object of type XmlSchemaSimpleTypeContent . This object needs to be cast to its appropriate type (restriction, list, or union) to access the type-specific methods. You then cast the Content property's return object to an XmlSchemaSimpleType Restriction object to access the facets collection. Listing 2.12 contains the complete code to give you perspective on the logic that's used to parse out a schema using the framework classes.

Listing 2.12 Accessing a Schema Using SOM and C#
 public void ReadSchemaXml(string xsdFile)            {                //Read the schema from file using the file:// moniker.                 System.Xml.XmlTextReader  reader = new  System.Xml.XmlTextReader(xsdFile);                 XmlSchemaCollection schemas = new XmlSchemaCollection();                 //Add the schema to the schemas collection                 schemas.Add(m_targetNameSpace,reader);                 foreach (XmlSchema  schema in schemas)                 {                     //We now have the schema object                      try                      {                          foreach ( XmlSchemaObject element in schema.Items)                           {                               if(element is XmlSchemaSimpleType)                                {                                    XmlSchemaSimpleType simpleType = graphics/ccc.gif XmlSchemaSimpleType)element;                                     System.Diagnostics.Debug.WriteLine ("<xsd:simpleType graphics/ccc.gif name=" + simpleType.Name );                                     XmlSchemaSimpleTypeContent content = graphics/ccc.gif simpleType.Content ;                                     if (content is XmlSchemaSimpleType Restriction)                                     {                              XmlSchemaSimpleTypeRestriction restriction = graphics/ccc.gif (XmlSchemaSimpleTypeRestriction ) simpleType.Content;                               System.Diagnostics.Debug.WriteLine("<xsd:restriction graphics/ccc.gif base=\"" + restriction.BaseTypeName + "\">");                               foreach(XmlSchemaFacet facet in restriction.Facets)                               {                        if (facet is XmlSchemaPatternFacet)                         {                              System.Diagnostics.Debug.WriteLine("<xsd:pattern value=\"" + graphics/ccc.gif facet.Value+ "\"/>");                                   }                                  }                               System.Diagnostics.Debug.WriteLine("</xsd:restriction>");                                        }                               System.Diagnostics.Debug.WriteLine("</xsd:simpleType");                         }               }          }          catch(System.Exception e)          {               System.Diagnostics.Debug.WriteLine(e.ToString());          }      }  } 

The same code in Visual Basic .NET is different regarding type conversion. The Visual Basic .NET version of the code, shown in Listing 2.13, relies on the Ctype function for casting and uses the TypeOf operator to determine if an object supports a given type.

Listing 2.13 Accessing a Schema Using SOM and Visual Basic .NET
 Public Sub ReadSchemaXml(ByVal xsdFile As String)      'Read the schema from file using the file:// moniker.      Dim reader As System.Xml.XmlTextReader = New System.Xml.XmlTextReader(xsdFile)      Dim schemas As XmlSchemaCollection = New XmlSchemaCollection()      'Add the schema to the schemas collection      schemas.Add(m_targetNameSpace, reader)      Dim schema As XmlSchema      For Each schema In schemas          'We now have the schema object          Try              Dim element As XmlSchemaObject              For Each element In schema.Items                  If TypeOf element Is XmlSchemaSimpleType Then                      Dim simpleType As XmlSchemaSimpleType = CType(element, graphics/ccc.gif XmlSchemaSimpleType)                      System.Diagnostics.Debug.WriteLine("<xsd:simple Typename=" + graphics/ccc.gif simpleType.Name)                      Dim content As XmlSchemaSimpleTypeContent = simpleType.Content                      If TypeOf content Is XmlSchemaSimpleTypeRestriction Then                          Dim restriction As XmlSchemaSimpleTypeRestriction = graphics/ccc.gif CType(simpleType.Content, XmlSchema SimpleTypeRestriction)                          System.Diagnostics.Debug.WriteLine("<xsd:restriction base=\"" + graphics/ccc.gif restriction.BaseTypeName + " \ ">")                          Dim facet As XmlSchemaFacet                          For Each facet In restriction.Facets                          If TypeOf facet Is XmlSchemaPatternFacet Then                                  System.Diagnostics.Debug.WriteLine("<xsd:pattern graphics/ccc.gif value=\"" + facet.Value+ " \ "/>")                              End If                          Next facet                          System.Diagnostics.Debug.WriteLine("</xsd:restriction>")                      End If  System.Diagnostics.Debug.WriteLine("</xsd:simpleType")                  End If              Next element          Catch errOops As System.Exception              System.Diagnostics.Debug.WriteLine(errOops.ToString())          End Try      Next schema  End Sub 

The output from Listing 2.12 and Listing 2.13 yields the following:

 urn:schemas-vbdna-net:framework  <xsd:simpleType name=zipType      <xsd:restriction base="http://www.w3.org/2001/XMLSchema:string">      <xsd:pattern value="\d{5}(-\d{4})?"/>    </xsd:restriction>  </xsd:simpleType  <xsd:simpleType name=emailType      <xsd:restriction base="http://www.w3.org/2001/XMLSchema:string">      <xsd:pattern value="\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*"/>    </xsd:restriction>  </xsd:simpleType 

Programmatic Validation with XmlValidatingReader

This section looks at programmatically validating XML documents against XML Schemas by using the XmlValidatingReader class. You must understand three main components when you're using the XmlValidatingReader : the reader, the XmlSchemaCollection object, and the ValidationEventHandler .

The XmlValidatingReader Class

Chapter 4 explains the concept of readers and writers in the .NET Framework in detail. That concept is introduced in this section by exploring the XmlValidatingReader class. XmlValidatingReader does precisely what its name implies: It reads XML and validates that XML against an XML Schema. XmlValidatingReader reads from a stream, so it does not actually hold the contents of the stream. It uses an instance of the XmlTextReader to read from the stream and extends the functionality of the XmlTextReader by validating the node against the specified schema(s). To associate the XmlTextReader instance with the XmlValidatingReader instance, you can use the constructor that accepts a text reader:

 //Instantiate a reader to read the XML file  XmlTextReader textReader = new XmlTextReader(xmlFile);  //Associate the ValidatingReader with the XMLTextReader object  XmlValidatingReader validator = new XmlValidatingReader(textReader); 
Associating a Schema with the XmlValidatingReader

After the XmlTextReader is associated with the XmlValidatingReader , you need to supply the schema(s) to the XmlValidatingReader instance. The XmlValidating Reader class exposes a Schemas collection that directs the validator to the schemas that are going to be used. The Schemas collection exposes an Add method that accepts four overloads to locate the schema(s) to be validated against. The first accepts an XmlSchemaCollection object, which is useful if you are working with multiple schemas:

 XmlSchemaCollection schemas = new XmlSchemaCollection();  schemas.Add ("urn:schemas-vbdna-net:framework", new XmlTextReader(xsdFile));  validatingReader.Schemas.Add(schemas); 

The second overload accepts a single XmlSchema object. As you saw earlier, using this object requires that you either build the schema on the fly or retrieve the schema from an XmlSchemaCollection object. The third overload accepts a namespace and an XmlReader abstract class. For this overload, you could specify an XmlTextReader because XmlTextReader is a concrete implementation of the XmlReader abstract class. This is nearly identical to the preceding usage of the XmlSchemaCollection object without a separate object reference:

 validatingReader.Schemas.Add("urn:schemas-vbdna-net:customer", new  XmlTextReader(xsdFile)); 

The last overload accepts a namespace for the schema and the URL to the physical file. You'll use this overload for the example, but the third overload can also be used:

 validatingReader.Schemas.Add("urn:schemas-xmlandasp net:customer","http://www.xmlandasp.net/schemas/fr.xsd"); 
Creating a ValidationEventHandler

As the XmlValidatingReader reads through the stream by using the supplied XmlTextReader , it validates the content in the stream to the schema specified and raises an event if the node violates the schema. This event is specified through its ValidationEvent property. Because the event is fired when the schema is invalid, you need a class member variable to determine, from the function containing the ValidatingReader , if the schema is valid or not.

To wire up the event handler and the XmlValidatingReader , declare a function that accepts two parameters ”an Object type and a ValidationEventArgs type:

 private void ValidationCallback ( object sender, ValidationEventArgs args )  { System.Diagnostics.Debug.WriteLine ("Validation error: {0}" + args.Message);  isValid = false;  } 

Inside this function, you have a simple debug output message that lets you know that a validation event occurred and what the message was. You can also set the class member variable to false to indicate that the validation was unsuccessful .

The next step is to create a ValidationEventHandler class that uses your created function as a callback. C# uses a delegate to wire up the event with the object, as shown here:

 // Wire up the callback to our ValidationCallBack routine  ValidationEventHandler eventHandler = new ValidationEventHandler  (ValidationCallback);  //Wire up the event handler to the validating reader's event handler  validator.ValidationEventHandler += eventHandler; 

In Visual Basic .NET, the same result can be accomplished by using the AddHandler method:

 'Wire up the callback to our ValidationCallBack routine  AddHandler validator.ValidationEventHandler, AddressOf ValidationCallback 

The XmlValidatingReader is now associated with your callback function and is fired when you begin the Read process.

Validating Using the Read Method

Until this point, you have set all the plumbing in place but still have not validated anything. The actual validation occurs when the Read method is called on each element within the XML instance document:

 while (validator.Read())  { //Here, you have access to each element.  We do nothing with this  //because we only want to know if the entire document is valid or not.  } 

The Read method simply reads the XML instance document and validates the element against the schema. If data is invalid or missing, the event handler is called.

Listing 2.14 contains the entire example in C#.

Listing 2.14 A Utility Class Used to Validate XML Documents Against a Specified Schema Using C#
 using System.Xml;  using System.Xml.Schema;  Public Class XMLFunctions  { private Boolean isValid = false;  //Private flag to hold validation results from callback  public Boolean IsXMLValid(string xsdFile, string xmlFile)  { //Instantiate a reader to read the XML file  XmlTextReader textReader = new XmlTextReader(xmlFile);  //Associate the ValidatingReader with the XMLTextReader object  XmlValidatingReader validator = new XmlValidatingReader(textReader);  validator.ValidationType = ValidationType.Auto;  //Create the schema collection  XmlSchemaCollection schemas = new XmlSchemaCollection();  schemas.Add (null, new XmlTextReader(xsdFile));  //Add the list of schemas to validate against to the schemas collection  validator.Schemas.Add(schemas);  // Wire up the callback to our ValidationCallBack routine  ValidationEventHandler eventHandler = new ValidationEventHandler (ValidationCallback);  //Wire up the event handler to the validating reader's event handler  validator.ValidationEventHandler += eventHandler;  while (validator.Read())  { //Here, you have access to each element.  We do nothing with this  //because we only want to know if the entire document is valid or not.  }  if (isValid)  System.Diagnostics.Debug.WriteLine ("Document is valid.");  else  System.Diagnostics.Debug.WriteLine ("Document is NOT valid.");  validator.Close();  textReader.Close();  Return isValid;  }  private void ValidationCallback ( object sender, ValidationEventArgs args )  { System.Diagnostics.Debug.WriteLine ("Validation error: {0}" + args.Message);  isValid = false;  }  } 

In Listing 2.15, you can see the same code in Visual Basic .NET.

Listing 2.15 A Utility Class Used to Validate XML Documents Against a Specified Schema Using C#
 Imports System.Xml  Imports System.Xml.Schema  Public Class XMLFunctions      Private isValid As Boolean = False  'Private flag to hold validation results from graphics/ccc.gif callback      Public Function IsXMLValid(ByVal xsdFile As String, ByVal xmlFile As String) As graphics/ccc.gif Boolean          'Instantiate a reader to read the XML file          Dim textReader As XmlTextReader = New XmlTextReader(xmlFile)          'Associate the ValidatingReader with the XMLTextReader object          Dim validator As XmlValidatingReader = New XmlValidatingReader(textReader)          validator.ValidationType = ValidationType.Auto          'Create the schema collection          Dim schemas As XmlSchemaCollection = New XmlSchemaCollection()          schemas.Add("urn:schemas-vbdna-net:framework", New XmlTextReader(xsdFile))          'Add the list of schemas to validate against to the schemas collection          validator.Schemas.Add(schemas)          'Wire up the callback to our ValidationCallBack routine          AddHandler validator.ValidationEventHandler, AddressOf ValidationCallback          While (validator.Read())              'Here, you have access to each element.  We do nothing with this              'because we only want to know if the entire document is valid or not.          End While          If (isValid) Then              System.Diagnostics.Debug.WriteLine("Document is valid.")          Else              System.Diagnostics.Debug.WriteLine("Document is NOT valid.")          End If          validator.Close()          textReader.Close()          Return isValid      End Function      Private Sub ValidationCallback(ByVal sender As Object, ByVal args As graphics/ccc.gif ValidationEventArgs)          System.Diagnostics.Debug.WriteLine ("Validation error: {0}" + args.Message);          isValid = False      End Sub  End Class 

The example in Listing 2.15 makes validating an XML document against a specified schema easy. Listing 2.16 shows a sample implementation that calls the XMLFunctions class that is defined in Listing 2.15.

Listing 2.16 A Sample Implementation of the XMLFunctions Class
 Imports System.Xml  Imports System.Xml.Schema  Public Class WebForm1      Inherits System.Web.UI.Page  #Region " Web Form Designer Generated Code "      'This call is required by the Web Form Designer.      <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()      End Sub      Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) graphics/ccc.gif Handles MyBase.Init          'CODEGEN: This method call is required by the Web Form Designer          'Do not modify it using the code editor.          InitializeComponent()      End Sub  #End Region      Private Sub Page_Load(ByVal sender As System.Object, ByVal e As  System.EventArgs) Handles MyBase.Load          'Put user code to initialize the page here          Dim test As XMLFunctions = New XMLFunctions()          Response.Write(test.IsXMLValid(Server.MapPath("test.xsd"),  Server.MapPath("testdoc.xml")))      End Sub  End Class 

Note the lack of error handling in this example. Both the C# and Visual Basic .NET versions make the assumption that both the XML and XSD files already exist. You might want to expand this example to include a try..catch block to trap errors that can occur when loading the XML or XSD files fails.

The code is fairly straightforward. Again, the concept of readers and writers will be explained in more detail in Chapter 6, "Exploring the System.Xml Namespace".

only for RuBoard


XML and ASP. NET
XML and ASP.NET
ISBN: B000H2MXOM
EAN: N/A
Year: 2005
Pages: 184

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net