More Complex Example: Controlling Serialization


If all we want to do is send objects from one .NET program to another, we don’t need any capability beyond what this first example just did. But more often, especially when talking to other platforms, you don’t control the XML document layout that other parties are expecting. The XML documents produced by your .NET objects need to conform to someone else’s XML schema—often created by a fractious industry standards body—that won’t be the default .NET layout. What we’d really like is to somehow control the XML document produced by the .NET serialization process so that we can make it match whatever external standards we care about.

You can control the XML document produced by the serialization process.

The .NET XML serializer allows us to do this quite easily by using .NET programming attributes. The previous example demonstrated the serializer’s default behavior, which stores each public member variable’s value in a separate XML element. But suppose the person sending me the XML document had placed these items in XML attributes instead. How could I read them incorrectly if the serializer is expecting elements? The answer turns out to be simple. I would modify my Point class as shown in Listing 7-6, decorating each member variable with the .NET programming attribute System.Xml.Serialization.XmlAttribute. (This attribute name is the abbreviated form of XmlAttributeAttribute, as explained in Chapter 2. The names are interchangeable.) When I create the serializer, it reads the .NET programming attributes from the metadata of the type that I pass it. When the serializer sees this attribute on a member variable, it knows that I want it to place the variable into an XML attribute rather than an XML element when serializing, and that it should expect to find the variable in an attribute when deserializing. The class shown in Listing 7-6 serializes to the document shown in Listing 7-7.

You control serialization by decorating class variables with .NET programming attributes.

Listing 7-6: Point class that serializes to attributes.

start example
Public Class AttributePoint <System.Xml.Serialization.XmlAttributeAttribute()> Public X As Integer <System.Xml.Serialization.XmlAttributeAttribute()> Public Y As Integer End Class
end example

Listing 7-7: XML representation of serialized class in attributes.

start example
<AttributePoint X="15" Y="20" /> 
end example

You’ll find that XmlAttribute and its companion XmlElement contain a number of variables allowing you even greater control over the XML produced by serializing your objects. You can, for example, make the element or attribute name in your XML document different from the name of the variable to which it corresponds in your .NET class. This allows your .NET variables to have names that aren’t legal in XML, such as names beginning with numbers. You can also add namespaces to your elements and attributes, or serialize to and from data types different from those in your .NET class. I won’t go into these operations in this book, and in fact your life will be significantly easier if you don’t waste your time with any of these shenanigans, instead allowing your .NET code to match your XML as closely as possible. But they’re there if you need them.

Advanced programmers can get even more control over the serialization process.

How about the case of serializing an object that contains other objects? As long as the contained objects are declared in the class, the serializer handles this case automatically. In fact, you’ve already seen an example of it and probably didn’t realize it. Integers, such as X and Y in the first example, are objects in the .NET hierarchy—value types, but objects nonetheless. As we can see, they serialized perfectly with no effort on our part. The serializer would also have worked properly had they been complex types of objects, perhaps themselves containing other objects, as long as the serializer knows what they are when you create it.

The serializer automatically handles the case of objects containing other objects.

What about the case where an object can contain objects of varying types? I’ve written a sample program that demonstrates this, shown in Figure 7-4.

click to expand
Figure 7-4: Sample program showing serialization of rectangle with different types of points.

Suppose I have a class that represents a rectangle, which I define by specifying the upper-left and lower-right points. Each of these can be of either class AttributePoint (shown previously in Listing 7-6), or ElementPoint (a point that serializes to elements, the default behavior shown in the first example). The default settings of the sample program produce a document containing one of each type of point. But the choice of using ElementPoints or AttributePoints is made at run time based on user input. Unlike the first example, the serializer couldn’t possibly know at the time I created it which type of point the rectangle would hold at serialization time. How can I configure my Rectangle class so that the serializer will properly handle whichever type it contains?

You can configure a class to obtain serialization information at run time.

It turns out that this isn’t hard either. Again, I make use of .NET programming attributes to instruct the serializer how to handle either case. As you can see in Listing 7-8, each of the points is represented by the type Object, which means that it can hold any .NET object. How does the serializer know what to serialize it as? You might think that it could know automatically, using the Reflection API at serialization time to determine what type of object it actually has and what the members of that class are. Unfortunately, the serializer doesn’t work that way. The designers thought that reading the reflection metadata at the moment of serialization would cost too much in terms of performance every time you read or wrote, so they insisted that the serializer know at the time it’s created all of the different classes to which the objects that it serializes might belong. The serializer can then perform the expensive operation of reading the object’s metadata and preparing its own internal data structures for serialization only once. You usually give the serializer the list of all the types it might encounter by decorating the variable with a .NET programming attribute of type System.Xml.Serialization.XmlElement for each class the object might be, in this case ElementPoint and AttributePoint. The sample program will properly handle either type, as you can see from the results shown in Listing 7-9.

You can also have a variable that can contain one of several types, as long as you specify which types at compile time.

Listing 7-8: Rectangle class that can hold either element points or attribute points.

start example
Public Class Rectangle <System.Xml.Serialization.XmlElement(GetType(ElementPoint)), _ System.Xml.Serialization.XmlElement(GetType(AttributePoint))> _ Public UpperLeft As Object Public LowerRight As Object End Class
end example

Listing 7-9: XML document produced by serializing rectangle.

start example
<Rectangle xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <ElementPoint> <X>5</X> <Y>10</Y> </ElementPoint> <LowerRight xsi:type="AttributePoint" X="15" Y="20" /> </Rectangle> 
end example

But wait! Why have I decorated only one of the points (UpperLeft) and not the other if the choice applies to both? Each instance of the serializer maintains an internal list of all the types it expects to see. When the serializer sees the decorations on the first variable, it adds the types to its list of possible types. Placing the same decorations on the second variable would simply add them to the serializer’s internal list again. In fact, you’ll get a run-time error if you attempt to decorate them both. Each attribute defining a type that you want the serializer to recognize must appear exactly once within each class, even if more than one variable of this type can appear. In fact, if I decorated one point with an attribute specifying the AttributePoint class and the other with the attribute specifying the ElementPoint class, either object could and would serialize as either class. That’s not what you would think as you read the code; it’s a clear and blatant violation of the Principle of Least Astonishment. Other .NET programming attributes don’t work this way, and neither do existing variable modifiers such as private or const. I understand that internally the serializer needs to see it only once, but I think that code would be easier to write, understand, and debug if the serializer simply ignored additional inclusions instead of throwing up.

You must include each potential type declaration exactly once—astonishing, but true.

But what if I don’t know all the possible types at compile time? You usually do because you usually work from an XML document schema, as I’ll describe in the next section. But if for some reason you don’t, you can override the design-time programming attributes (or lack thereof) at run time. You create an object of class System.Xml.Serialization.XmlAttributeOverrides, containing the XmlElement and XmlAttribute declarations that your run-time operations have convinced you that you want. You pass this object to the constructor of the serializer object, which will tell the serializer to override the design time decorations. I don’t show this operation because it’s more work; but if you can’t live without it, it’s there.

You can override the compile-time behavior at run time with code.

What if I don’t know or don’t want to take the trouble to find out all the potential types of elements I might see, even at run time? This is a common scenario, as most large XML schemas contain a provision for accepting user- defined fields. If you read someone’s XML document because you cared about the standard fields, but you didn’t know about the user-defined fields that the sender had placed in the document, your serialization process would choke. You can tell the serializer to accept any type of unrecognized input by decorating a variable with the XmlAnyElement or XmlAnyAttribute programming attributes. These cause any unrecognized elements or attributes to be placed in an array of objects of type System.Xml.XmlElement or System.Xml.XmlAttribute, respectively. You can then work your way through these objects to see what you’ve actually been given, or just ignore them. If you don’t decorate a variable of this type, an unrecognized field in the XML document will cause the serialization process to fail. Unless you are forbidden to accept a document containing an unwanted XML element or attribute, you might want to form the habit of putting in these attributes as sort of an overflow mailbox, just in case.

You can also use programming attributes to accept XML elements and attributes that don’t correspond to your .NET class.




Introducing Microsoft. NET
Introducing Microsoft .NET (Pro-Developer)
ISBN: 0735619182
EAN: 2147483647
Year: 2003
Pages: 110

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