Você está na página 1de 17

XML Serialization

The .NET Framework supports yet another form of object serialization known as XML serialization (not to be confused with SOAP serialization). In a nutshell, XML serialization allows you to persist an objects state in an XML stream while maintaining control over the XML elements used to persist data. For example, you can decide the XML namespace to use, whether a property should be serialized as an XML element or attribute, and the name of that element or attribute. (You dont have such control when youre persisting to SOAP format.) While XML serialization sounds more flexible than SOAP serialization, it has a few shortcomings as well:

It works only with public classes. Only public fields and properties can be serialized. An object graph cant be serialized if it contains circular references. Data in the class is serialized, but object identity is lost, as is information about the assembly.

Whats XML serialization good for? Most often youll use this technique to quickly serialize your objects in an XML stream whose syntax is understood by another application. The opposite operation is also quite commonthat is, reading XML data coming from another application into your objects.

The XmlSerializer Class


The workhorse of XML serialization is the System.Xml.Serialization.XmlSerializer object, which works pretty much like the SoapFormatter object. The main difference is that each XmlSerializer object is good only for serializing the class specified in its constructor. To see how XML serialization works, lets start with a simple class:
Public Class Customer Public ID As Integer Public Name As String Public Address As String Public City As String ' All XML-serializable classes must support a parameterless ' constructor. Sub New() ' There's nothing to do in this example. End Sub Sub New(ByVal id As Integer, ByVal name As String, _ ByVal address As String, ByVal city As String) Me.ID = id Me.Name = name Me.Address = address Me.City = city End Sub End Class

Lets see how you can create an XmlSerializer object that saves an instance of the Customer class to a file and then reloads it:
' This code assumes that the following Imports are used: ' Imports System.Xml.Serialization ' Imports System.IO Sub TestXmlSerializer() ' Create an XmlSerializer object for the Publisher class. Dim ser As New XmlSerializer(GetType(Customer)) ' Create a Customer object. Dim cust As New Customer(1, "Joe Doe", Nothing, "New York") ' Open the destination file. Dim fs As New FileStream("c:\customer.xml", FileMode.Create) ' Serialize the object to the stream, and close it. ser.Serialize(fs, cust) fs.Close() ' Reopen the stream. Dim fs2 As New FileStream("c:\customer.xml", FileMode.Open) ' Deserialize the file into another Customer object, and ' close the stream. Dim cust2 As Customer = CType(ser.Deserialize(fs2), Customer) fs2.Close() ' Check object properties. ' Joe Doe, New York Console.WriteLine(cust2.Name & ", " & cust2.City) End Sub

Heres the XML text in the c:\customer.xml file, after I indented some lines to make the XML structure more evident:
<?xml version="1.0"?> <Customer xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <ID>1</ID> <Name>Joe Doe</Name> <City>New York</City> </Customer>

As you see, the name of the class is used for the outermost XML root element (Customer), and all fields are rendered as nested XML elements. Interestingly, if a property is Nothing (as is the case with the Address property), its rendered with an explicit xsi:nil XML attribute. Note that the XmlSerializer object deals correctly with characters inside String fields or properties that have a special meaning in XML by converting them to their escaped forms (for example, &lt; for the < character and &gt; for the > character).

Serialization Attributes
You have precise control over the XML serialization process, thanks to a group of attributes that let you modify how individual fields of the class are persisted. Among such attributes are these:

The XmlRoot attribute lets you set the name used for the outermost XML root element, the namespace for the element, and whether the xsi:null attribute appears if the object being persisted is Nothing. The XmlElement attribute lets you specify a different name for the XML element corresponding to a given field or property. It also lets you decide whether the element should appear at all if its Nothing. The XmlAttributeAttribute attribute lets you flag fields and properties that should be serialized as attributes rather than elements, decide which name should be used for the attribute, and decide whether it should be omitted if the fields value is Nothing. (Note that you cant shorten this attributes name in code to XmlAttribute.) The XmlText attribute lets you specify that a field or a property should be rendered as XML text without being enclosed in XML tags. The XmlIgnore attribute lets you flag a field or a property that shouldnt be serialized at all.

(See Table 1 for the complete list of supported attributes.) Lets create a new Customer2 class that exploits some of these attributes:
<XmlRootAttribute("Customer", _ Namespace:="http://www.vb2themax.com", IsNullable:=False)> _ Public Class Customer2 <XmlAttributeAttribute("CustId")> _ Public ID As Integer <XmlElement("name")> _ Public Name As String <XmlIgnore()> _ Public Address As String <XmlElement("city", IsNullable:=False)> _ Public City As String <XmlText()> _ Public Notes As String ' ...(Constructors as in Customer class)... End Class

The XmlElement attribute can take optional values. The IsNullable value specifies the behavior when the field or property is Nothing: if True, the XML element is preserved and an xsi:nil XML attribute is appended; if False or omitted, the XML element is discarded when the fields value is Nothing.
Serializing the Customer2 class delivers this XML text. (Differences are in boldface.)<?xml version="1.0" ?> <Customer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" CustId="1" xmlns="http://www.vb2themax.com"> <name>Joe Doe</name> <city>New York</city> </Customer>

As you see, the ID field was rendered into the CustId attribute, the Address field wasnt serialized at all (because its Nothing), the root element is qualified with the http://www.vb2themax.com namespace, and remaining XML elements are lowercase.

The root XML element is still Customer, even if the class name has changed to Customer2, thanks to the XmlRoot attribute. (We might have used an XmlType attribute as well.) The constructor of both the XmlAttributeAttribute and XmlElement attributes can also take a Namespace value, which specifies the namespace for the generated attribute or element. To see how the Namespace value affects the result, modify the Customer2 class as follows:
Public Class Customer3 <XmlAttributeAttribute("CustId", Namespace:="www.abc.com")> _ Public ID As Integer <XmlElement("name", Namespace:="www.abc.com")> _ ' ...(The remainder of the class as before)... End Class

The new version of the class produces this XML text. (Differences are in boldface.)
<?xml version="1.0" ?> <Customer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" n1:CustId="1" xmlns:n1="www.abc.com" xmlns="http://www.vb2themax.com"> <n1:name>Joe Doe</n1:name> <city>New York</city> </Customer>

The XmlArray and XmlArrayItem attributes are usually applied together to fields and properties that return an array of complex objects that are rendered as nested XML elements. The XmlArray attribute defines the name of the outer element, whereas the XmlArrayItem attribute defines the name of the inner element. For example, lets define a Customer4 class that contains an array of Order objects:
Public Class Customer4 <XmlArray("CustOrders"), XmlArrayItem("CustOrder", _ IsNullable:=True)> _ Public Orders(3) As Order ' ...(All other members as before)... End Class Public Class Order <XmlAttributeAttribute("OrderId")> _ Public ID As Integer Public [Date] As Date ' Note how we deal with a ' Visual Basic reserved ' word. Public Total As Decimal ' All XML serializable classes must have a default ' constructor. Sub New() ' There's nothing to do in this example. End Sub Sub New(ByVal id As Integer, ByVal [date] As Date, _ ByVal total As Decimal) Me.ID = id

Me.Date = [date] Me.Total = total End Sub End Class

This is the XML text produced by a Customer4 object that has two child Order objects:
<?xml version="1.0" ?> <Customer4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" n1:CustId="1" xmlns:n1="www.abc.com"> <n1:name>Joe Doe</n1:name> <city>New York</city> <CustOrders> <CustOrder OrderId="1"> <Date>2001-01-02T00:00:00.0000000+01:00</Date> <Total>123.5</Total> </CustOrder> <CustOrder OrderId="2"> <Date>2001-04-08T00:00:00.0000000+02:00</Date> <Total>450.8</Total> </CustOrder> <CustOrder xsi:nil="true" /> <CustOrder xsi:nil="true" /> </CustOrders> </Customer4>

The XmlArrayItem element can take an IsNullable property. If this property is set to True, the XML text contains xsi:nil attributes for each child object set to Nothing (as you see in the last two elements of the CustOrders array in the preceding code, shown in boldface). If the IsNullable property is set to False or omitted, null child objects dont appear in the XML result. Table 1 Attributes to Control XML Serialization (Taken from .NET Documentation) Attribute Applies To Description

XmlAnyAttributeAttribute Public fields that return During deserialization, the array will an array of XmlAttributebe filled with XmlAttribute objects objects. that represent all XML attributes unknown to the schema. XmlAnyElementAttribute Public fields that return During deserialization, the array will an array of XmlElement be filled with XmlElement objects objects. that represent all XML elements unknown to the schema. XmlArrayAttribute Public properties and The members of the array will be fields that return arrays generated as members of an XML of complex objects. array. Public properties and Derived types that can be inserted fields that return arrays into an array. of complex objects.

XmlArrayItemAttribute

Attribute XmlAttributeAttribute XmlElementAttribute XmlEnumAttribute XmlIgnoreAttribute

Applies To Public properties and fields. Public properties and fields.

Description The class will be serialized as an XML attribute. The field or property will be serialized as an XML element.

Enumeration identifiers. The element name of an enumeration member. Public properties and fields. The property or field should be ignored when the containing class is serialized.

XmlIncludeAttribute

Public derived class The class should be included when declarations, and public generating schemas (and will thus be methods. recognized when serialized). Public class declarations. The class represents the root element of the XML document. (Use the attribute to further specify the namespace and the element name.) The property or field should be serialized as XML text. The class should be serialized as an XML type; is ignored if used together with XmlRootAttribute. (Use the attribute to further specify the namespace and the element name.)

XmlRootAttribute

XmlTextAttribute XmlTypeAttribute

Public properties and fields. Public class declarations.

The XmlSerializerNamespaces Object


In the preceding section, you saw that the XmlAttributeAttribute and XmlElement attributes support a Namespace value, which gives you some control over the XML namespace of the serialized value. You can use another method for changing the default namespace of elements that is based on the XmlSerializerNamespaces auxiliary object and that doesnt require you to embed an attribute in the class code. Using the XmlSerializerNamespaces object is quite simple: you use its Add method to associate a namespace with a given XML element, and then you pass the object as the last argument of the XmlSerializer.Serialize method:

Dim ns As New XmlSerializerNamespaces() ' Change the namespace of the Customer4 element. s.Add("Customer4", "http://www.vb2themax.com") ' Change the namespace of the Order element. s.Add("Order", "http://www.wintellect.com") ' Open the destination file. Dim fs As New FileStream("c:\customer.xml", FileMode.Create) ' Serialize the object to the stream, enforcing the specified ' namespaces. ser.Serialize(fs, cust, ns)

This is the XML text produced by the previous code:


<?xml version="1.0" ?> <Customer4 xmlns:Customer4="http://www.vb2themax.com" xmlns:Order="http://www.wintellect.com" n1:CustId="1" xmlns:n1="www.abc.com"> <CustOrders> <CustOrder OrderId="1"> <Date>2001-01-02T00:00:00.0000000+01:00</Date> <Total>123.5</Total> </CustOrder> <CustOrder OrderId="2"> <Date>2001-04-08T00:00:00.0000000+02:00</Date> <Total>450.8</Total> </CustOrder> <CustOrder n1:nil="true" xmlns:n1="http://www.w3.org/2001/XMLSchema-instance" /> <CustOrder n1:nil="true" xmlns:n1="http://www.w3.org/2001/XMLSchema-instance" /> </CustOrders> <n1:name>Joe Doe</n1:name> <city>New York</city> </Customer4>

Deserialization Events
The XmlSerializer object can raise four different events during the deserialization process if it encounters any XML entity it doesnt recognize. Two of these events are particularly interesting for our purposes: the UnknownElement and UnknownAttribute events, which fire when the XmlSerializer object reads an unrecognized XML element and attribute. If you dont write any code for these events, the XmlSerializer object simply ignores elements that it doesnt recognize. You can use deserialization events for a variety of purposesfor example, to keep a log of elements that failed to convert correctly. A better use for these events, however, is to provide a way to import XML data that doesnt exactly match the structure of your class. For example, say that you want to import this XML file into a Customer class:
<?xml version="1.0" ?> <Customer xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <ID>1</ID> <FirstName>Joe</FirstName> <LastName>Doe</LastName> <Address xsi:nil="true" />

<City>New York</City> </Customer>

The boldface portion highlights the problem: instead of having one Name element, this file contains two distinct elements, FirstName and LastName. Unless you take additional steps, the XmlSerializer object ignores these two pieces of information, and the Customer.Name property remains unassigned. Thanks to the UnknownElement event, however, you can trap these elements and combine them into a value that you later assign to the Name property. Heres the code that does the trick:
Sub TestDeserializationEvents() ' Create an XmlSerializer object for the Publisher class. Dim ser As New XmlSerializer(GetType(Customer)) ' Dynamically create the event. AddHandler ser.UnknownElement, _ AddressOf Deserialization_UnknownElement ' Reopen the stream. Dim fs2 As New FileStream("c:\customer2.xml", FileMode.Open) ' Deserialize the file into another Customer object, and ' close the stream. Dim cust2 As Customer = CType(ser.Deserialize(fs2), Customer) fs2.Close() ' Check that the name was read correctly. ' Joe Doe, New York Console.WriteLine(cust2.Name & ", " & cust2.City) End Sub ' This event is raised when the XmlSerializer finds an unknown ' XML element. Sub Deserialization_UnknownElement(ByVal sender As Object, _ ByVal e As XmlElementEventArgs) ' Cast the element to a Customer object. Dim cust As Customer = CType(e.ObjectBeingDeserialized, _ Customer) ' There are two cases: we've found either a FirstName or a ' LastName XML element. If e.Element.Name = "FirstName" Then ' If it is a FirstName element, assign to Name property. cust.Name = e.Element.InnerXml ElseIf e.Element.Name = "LastName" Then ' If it is a LastName element, append to Name property. cust.Name &= " " & e.Element.InnerXml End If End Sub

The XmlElementEventArgs object exposes four interesting properties:

ObjectBeingDeserialized Its name says it all: this is the object currently deserialized. You can test its type to learn which object is failing to be deserialized correctly, or you can assign its fields and properties (as the preceding event procedure does). LineNumber and LinePosition These properties return the line and the column number where the unrecognized element has been encountered. Theyre useful for reporting errors in the incoming XML data. Element This XmlElement object represents the unrecognized XML element; it exposes several properties, the most important of which are Name (the name of the element) and InnerXml (its contents).

The UnknownAttribute event receives an XmlAttributeEventArgs object, which exposes the following properties: ObjectBeingDeserialized, LineNumber, LinePosition, and Attr. You already know about the first three properties. Attr is an XmlAttribute object that represents the unrecognized XML attribute; this object exposes several properties, the most important of which are Name (the name of the attribute) and Value (its contents).

Overriding Behavior
The XML serialization mechanism seen so far has two major drawbacks. First, it assumes that you have the source code of the class being serialized, so it doesnt work with classes in a compiled DLL or with classes that you inherit from a class in a DLL. Second, the serialization format is fixed for each given class, so you cant serialize a class in two or more different ways (which might be necessary if youre importing data from one application, processing it, and exporting to a different application). Overriding the default behavior of the XmlSerializer class can solve both these issues. Assume that you have the AddressBook class, which contains an array of Person objects:
Class AddressBook Public Name As String Public Contacts() As Person End Class Class Person Public ID As Integer Public Name As String Public PhoneNumber As String End Class

You can serialize these classes using an XmlSerializer object, as I have shown you previously. For example, this is the XML that you get when you serialize an AddressBook object that contains two Person objects in its Contacts array:
<?xml version="1.0" ?> <AddressBook xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">

<Name>My Address Book</Name> <Contacts> <Person> <ID>1</ID> <Name>Joe Doe</Name> <PhoneNumber>234-555-6789</PhoneNumber> </Person> <Person> <ID>2</ID> <Name>Robert Smith</Name> <PhoneNumber>234-555-6543</PhoneNumber> </Person> </Contacts> </AddressBook>

Lets suppose that you want to modify this XML output because you must pass it to an application that recognizes XML input in a slightly different format. For example, say that you want to change the output as follows. (Changes are in boldface.)
<?xml version="1.0" ?> <EmployeeList xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Name>My Employee Book</Name> <Employees> <Employee ID="1"> <Name>Joe Doe</Name> <Phone>234-555-6789</Phone> </Employee> <Employee ID="2"> <Name>Robert Smith</Name> <Phone>234-555-6543</Phone> </Employee> </Employees> </EmployeeList>

If you can modify the source code of the AddressBook and Person classes, getting this new format requires only a few appropriate Xmlxxxx attributes:
<XmlRootAttribute("EmployeeList")> _ Public Class AddressBook Public Name As String <XmlArray("Employees"), XmlArrayItem("Employee")> _ Public Contacts() As Person End Class Public Class Person <XmlAttributeAttribute("ID")> _ Public ID As Integer Public Name As String <XmlElement("Phone")> _ Public PhoneNumber As String End Class

Heres the problem: what if you cant modify the source code of these classes (for example, because you must read or write XML also in the default format) or you dont even have their source code (because these classes are defined in a compiled

DLL)? In these circumstances, your only option is to override the default behavior of the XmlSerializer object. In practice, you use an XmlAttributeOverrides object to specify which attributes you would add to source code if you were allowed to do so. Heres the complete sequence of actions you must perform: 1 2 Create an XmlAttributeOverrides object. Create an XmlAttributes object. (Note the plural.) This object will contain one or more XmlxxxxAttribute objects, one for each attribute that you want to apply to a field or property that must be overridden. Create an XmlxxxxAttribute object, which represents an attribute that you want to apply to a given field or property. For example, you can create an XmlElementAttribute object to render a field or property as an XML element or an XmlAttributeAttribute object to render a field or property as an XML attribute. This object is the same XmlxxxxAttribute attribute that you would insert in the source code of the class being serialized; and in fact, you can use the attributes constructor as you do when inserting the attribute between < and > brackets. Or you can assign individual properties, such as ElementName (the name of the XML element or attribute that will be rendered) and Type (the class that renders that specific element or attribute). Assign the XmlxxxxAttribute that you have created at a previous point to the appropriate property of the XmlAttributes object you created in step 2. For example, you assign an XmlTextAttribute to the XmlAttributes.XmlText property. If an attribute can be specified multiple times, youll have to add it to a suitable collection that the XmlAttributes object exposes. (For example, you add an XmlElementAttribute object to the XmlAttributes.XmlElements collection.) Repeat steps 3 and 4 for each attribute that you want to assign to a given element. 5 Use the Add method of the XmlAttributeOverrides object to add the XmlAttributes object you created in step 2. The XmlAttributeOverrides.Add method takes both a System.Type object corresponding to the class that contains the overridden member and the name of the XML element or attribute being overridden. Repeat steps 2 through 5 for each XML syntax entity that you want to override. 6 Finally, create the XmlSerializer object, passing the XmlAttributeOverrides object to the second argument of its constructor.

As you see, its a long and winding road, so an actual code example is in order. The following procedure produces the modified XML text seen previously by virtually inserting new attributes in the AddressBook and Person classes. The comments in the following code refer to the steps mentioned in the preceding list, so you should have no problem following the execution flow:
Sub TestSerializationOverriding() ' Step 1: create an XmlAttributeOverrides object. Dim xmlAttrOver As New XmlAttributeOverrides()

' (A) Add the XmlRootAttribute("EmployeeList") attribute. ' Step A2: create the XmlAttributes object. Dim xmlAttrs As New XmlAttributes() ' Step A3: create and initialize the XmlRootAttribute object. Dim attr1 As New XmlRootAttribute("EmployeeList") ' Step A4: assign it to the correct property of the ' XmlAttributes object. xmlAttrs.XmlRoot = attr1 ' Step A5: pass XmlAttributes to the Add method of ' XmlAttributeOverrides. ' When adding an XmlRootAttribute, you specify only two ' arguments: ' first argument is the class that contains the overridden ' member; ' second argument is the XmlAttributes ' object that defines the new attributes. xmlAttrOver.Add(GetType(AddressBook), xmlAttrs) ' (B) Add XmlArray("Employees") and ' XmlArrayItem("Employee") attributes. ' Step B2: create a new XmlAttributes object. xmlAttrs = New XmlAttributes() ' Step B3: create and initialize the XmlArrayAttribute ' object. Dim attr2 As New XmlArrayAttribute("Employees") ' Step B4: assign it to the correct property of the ' XmlAttributes object. xmlAttrs.XmlArray = attr2 ' Repeat steps B3/B4, this time for the XmlArrayItemAttribute ' object. (Note how you can do both creation and assignment ' in one step.) xmlAttrs.XmlArrayItems.Add( _ New XmlArrayItemAttribute("Employee")) ' Step B5: pass XmlAttributes to the Add method of ' XmlAttributeOverrides: ' first argument is the class that contains the overridden ' member; ' second argument is the member being overridden; ' third argument is the XmlAttributes object that defines ' the new attributes. xmlAttrOver.Add(GetType(AddressBook), "Contacts", xmlAttrs) ' (C) Add the XmlAttributeAttribute("ID") attribute. ' Step C2: create a new XmlAttributes object. xmlAttrs = New XmlAttributes() ' Steps C3/C4: create and initialize the XmlArrayAttribute ' object,and assign it to the proper property of the ' XmlAttributes object. xmlAttrs.XmlAttribute = New XmlAttributeAttribute("id") ' Step C5: pass XmlAttributes to the Add method of ' XmlAttributeOverrides. ' (Arguments are as in step B5.) xmlAttrOver.Add(GetType(Person), "ID", xmlAttrs)

' (D) Add the XmlElement("Phone") attribute. ' Step D2: create a new XmlAttributes object. xmlAttrs = New XmlAttributes() ' Steps D3/D4: create and initialize the XmlElementAttribute ' object, and add it to XmlAttributes' XmlElements ' collection. xmlAttrs.XmlElements.Add(New XmlElementAttribute("Phone")) ' Step D5: pass XmlAttributes to the Add method of ' XmlAttributeOverrides. ' (Arguments are as in step B5.) xmlAttrOver.Add(GetType(Person), "PhoneNumber", xmlAttrs) ' Step 6: create the XmlSerializer that uses ' XmlAttributeOverrides. Dim ser As New XmlSerializer(GetType(AddressBook), _ xmlAttrOver) ' Serialize an AddressBook object using XmlSerializer. ' ...(omitted)... End Sub

Unfortunately, what weve done so far doesnt suffice in many real-world applications. For example, lets say that you have two classes that inherit from PersonEmployee and CandidateEmployee:
Class Employee Inherits Person Public SSN As String Public HireDate As String End Class Class CandidateEmployee Inherits Person Public InterviewDate As Date End Class

You might want to store both the Employee and CandidateEmployee objects in the AddressBook.Contacts array, but you want to render custom XML text for these classes. For example, lets say you want to suppress the HireDate property and render the SSN property as an XML attribute. This is a sample of the XML text you want to achieve:
<?xml version="1.0" ?> <EmployeeList xmlns:xsi="http://www.w3.org/2001/XMLSchemainstance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <Name>My Employee Book</Name> <Employees> <Employee id="1"SSN="111-222-3333"> <Name>Joe Doe</Name> <Phone>234-555-6789</Phone> </Employee> <CandidateEmployee id="2"> <Name>Robert Smith</Name> <Phone>234-555-6543</Phone> <InterviewDate>2001-02-08</InterviewDate> </CandidateEmployee> </Employees> </EmployeeList>

The preceding XML text corresponds to the application of the following attributes to the AddressBook and Employee classes. (Additions are in boldface.)
<XmlRootAttribute("EmployeeList")> _ Public Class AddressBook Public Name As String <XmlArray("Employees"), XmlArrayItem("Employee", GetType(Employee)), _ XmlArrayItem("CandidateEmployee", _ GetType(CandidateEmploye e)) > Public Contacts() As Person End Class Class Employee Inherits Person <XmlAttributeAttribute("SSN")> _ Public SSN As String <XmlIgnore()> _ Public HireDate As String End Class

Note that you need two XmlArrayItem attributes, each one describing the XML element that must be used for a different object that can be contained in the Contacts array. Heres a revised routine that creates an XmlSerializer object capable of serializing and deserializing the AddressBook object tree using the custom XML format. To save space, Im showing only code changes and additions (specific differences in boldface), but the complete source code is on the companion CD.
Sub TestSerializationOverriding2() ' Step 1: create an XmlAttributeOverrides object. Dim xmlAttrOver As New XmlAttributeOverrides() ' ...(Section A as before)... ' (B) Add XmlArray("Employees") and XmlArrayItem("Employee") ' attributes. ' Step B2: create a new XmlAttributes object. xmlAttrs = New XmlAttributes() ' Steps B3/B4: create and initialize the XmlArrayAttribute ' object, and assign it to the correct property of the ' XmlAttributes object. xmlAttrs.XmlArray = New XmlArrayAttribute("Employees") ' Repeat steps B3/ B4, this time for the two ' XmlArrayItemAttribute objects. xmlAttrs.XmlArrayItems.Add(New XmlArrayItemAttribute( _ "Employee", GetType(Employee))) xmlAttrs.XmlArrayItems.Add(New XmlArrayItemAttribute( _ "CandidateEmployee", GetType(CandidateEmployee))) ' Step B5: pass XmlAttributes to the Add method of ' XmlAttributeOverrides: ' first argument is the class that contains the overridden ' member; ' second argument is the member being overridden; ' third argument is the XmlAttributes object that defines ' the new attributes. xmlAttrOver.Add(GetType(AddressBook), "Contacts", xmlAttrs)

' ...(Sections C-D as before)... ' (E) Add the XmlAttribute() attribute to SSN element in ' Employee class. ' Step E2: create a new XmlAttributes object. xmlAttrs = New XmlAttributes() ' Steps E3/E4: create and initialize the XmlAttribute object, ' and assign it to the correct property of XmlAttributes ' object. xmlAttrs.XmlAttribute = New XmlAttributeAttribute("SSN") ' Step E5: pass XmlAttributes to the Add method of ' XmlAttributeOverri des. xmlAttrOver.Add(GetType(Employee), "SSN", xmlAttrs) ' (F) Add the XmlIgnore() attribute to HireDate element in ' Employee class. ' Step F2: create a new XmlAttributes object. xmlAttrs = New XmlAttributes() ' Steps F3/F4: note that you don't actually create an ' XmlIgnoreAttribute object; for all those attributes whose ' presence is enough to affect the serialization behavior, ' you just set the corresponding property to True. xmlAttrs.XmlIgnore = True ' Step F5: pass XmlAttributes to the Add method of ' XmlAttributeOverrides. xmlAttrOver.Add(GetType(Employee), "HireDate", xmlAttrs) ' Step 6: create the XmlSerializer that uses ' XmlAttributeOverrides. Dim ser As New XmlSerializer(GetType(AddressBook), _ xmlAttrOver) ' Serialize an AddressBook object using the XmlSerializer. ' ...(omitted)... End Sub

Working with XSD Schemas


The .NET Framework comes with a great tool named xsd.exe. This is a command-line utility that can perform several useful tasks related to XML Schema Definition (XSD) files. For example, you can feed it an assembly file (a DLL or an EXE file) and have it create one or more XSD files that describe all the public types in that assembly:
xsd myasm.exe

By default, XSD files are created in the current directory and are named schema0.xsd, schema1.xsd, and so on. You can redirect the output to another directory with the /out option. (All the options of this utility can be shortened to their first character.)
xsd myasm.exe /o:anotherdir

You can also generate the schema for just one of the types defined in the assembly by using the /type option (beware: type name is case sensitive):
xsd myasm.exe /t:Customer

For example, this is the XSD produced for the Person class used in the preceding section:
<?xml version="1.0" encoding="utf-8"?> <schema attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace="" xmlns="http://www.w3.org/2001/XMLSchema"> <element name="Person" nillable="true" type="Person" /> <complexType name="Person"> <sequence> <element minOccurs="1" maxOccurs="1" name="ID" type="int" /> <element minOccurs="1" maxOccurs="1" name="Name" nillable="true" type="string" /> <element minOccurs="1" maxOccurs="1" name="PhoneNumber" nillable="true" type="string" /> </sequence> </complexType> </schema>

Even if you arent an XSD wizard, you can clearly see that the XSD file embeds detailed information about the class and its fields. If the class contains Xmlxxxx attributes that redefine the names of elements in the XML output, these names appear in the XSD file as well. In other words, this XSD defines the serialization structure of the type, not necessarily its source code definition. You can affect the contents of the XSD schema by embedding XmlType attributes in your source code. You can apply this attribute to a Visual Basic class and decide the name of the corresponding XML element in the XSD schema, its namespace, and whether it should appear at all in the schema. In the following example, the AddressBook class isnt exported to the XSD and the Person class appears in the schema as the Contact element:
<XmlType(IncludeInSchema:=False)> _ Public Class AddressBook Public Name As String Public Contacts() As Person End Class <XmlType("Contact", Namespace:="www.vb2themax.com")> _ Public Class Person Public ID As Integer Public Name As String Public PhoneNumber As String End Class

The beauty of XSD files is that they thoroughly and unambiguously define the structure of your classes, so you can hand these files to other programmers without giving the classes source code. In this sense, an XSD file is a bit like the type library that accompanies COM components. The xsd.exe utility can perform the transformation in the opposite direction as well, and it can convert an XSD file to a set of classes written in either C# or Visual Basic .NET. This operation requires that you use the /classes option to specify that you want to generate one or more classes, and you use the /language option to indicate

which language is to be generated. (The language identifier can be C#, VB, or JS for JavaScript.) These two options can be shortened to /c and /l, respectively:
xsd schema1.xsd /c /l:vb

You can specify the element or elements in the schema for which you want to generate code by using one or more /element options. (If the / element options are omitted, the utility generates code for all classes.) You can also specify the namespace for the generated types by using the / namespace option. See the .NET SDK documentation for more details about the xsd.exe utility and all its command-line options. Heres a scenario in which the xsd.exe utility can be useful. Say that you have an application that serializes its types to XML and you want to give other developers the ability to read this XML output (and maybe resubmit another XML stream with their changes). Understandably, you feel uncomfortable with the idea of giving them your source code, and fortunately you dont have to. In fact, you can use xsd.exe to generate an XSD file that accurately describes the XML coming out of your application. Other developers can feed xsd.exe with this XSD file and create one or more classes that are isomorphic with your original classes by using the /classes option. (Interestingly, they can use the /language option to generate the source code for these classes in a programming language thats different from the language you used to create the original classes.) After developers create a set of classes that mirror the original classes in your application, they can use an XmlSerializer object to read your XML data into their objects and write any changes back to the same or another XML file so that you can read back their changes. (In practice, you might not use files at all and work with data sent through HTTP or SOAP.) This approach clearly gives you an unparalleled degree of flexibility interoperating with other applications (not necessarily running on the same machine).

Você também pode gostar