The WCF XML Stack


The Microsoft .NET Framework defines a rich set of types for general-purpose XML processing. As a messaging platform, WCF requires more functionality than what is normally required by other .NET applications. For example, you saw in Chapter 2, “Service Orientation,” that WCF can generate, send, receive, and process binary and MTOM-encoded XML messages. Because the .NET Framework does not provide this capability, the WCF API defines types that do provide it, and we use these types to interact directly with the Message type. In other words, the WCF API defines types that transform a Message into a particular encoding. With this in mind, there are three key types defined in the WCF System.Xml namespace of the System.Runtime.Serialization.dll assembly that are fundamentally responsible for serializing and encoding the Message type: XmlDictionary, XmlDictionaryWriter, and XmlDictionaryReader. To keep the discussion of these types as simple as possible, I will illustrate these types by working with XML fragments rather than fully formed SOAP messages. Later in this chapter, you will see how these types can be used to serialize and encode instances of the Message type.

The XmlDictionary Type

As its name implies, an XmlDictionary object is a mapping of key-value pairs. Much like a language dictionary or vocabulary, an XmlDictionary can be used to substitute a simple expression for a complex one without losing any meaning. We use this type of mechanism in our everyday lives without even realizing it. Consider the following sentence I say to my friend Rusty: “I watched a movie last night about a submarine.” Rusty will hear this sentence and interpret it to mean “I watched a movie last night about a vessel that functions on the surface and underwater.” The first sentence is clearly shorter than the second sentence, and it requires less time to express. This compression and the resultant time savings are possible because Rusty and I share a vocabulary. As long as Rusty and I understand the same vocabulary, both of us can communicate efficiently. If, however, I say to Rusty: “This chapter was finished by sheer elucubration,” he might have no idea what I’m talking about. In this instance, I have ruined the overall time savings and efficiency by using a word that Rusty does not understand. In effect, a dictionary (or in this example, a vocabulary) increases efficiency only if it is known to all participants.

At the risk of flogging this analogy to death, there is one more lesson that it illustrates. When I say to Rusty, “I watched a movie last night about a submarine,” the entire sentence itself symbolizes meaning that can be expressed several different ways, and in several different languages. If you know what the words movie and submarine mean, you probably envision, in your mind’s eye, a dark theater (and maybe even the smell of $5.00 popcorn) and the silhouette of a submarine, respectively. In other words, the words in the sentence invoke images of “things” in the real world. In terms of the XML Infoset and encodings, you map the XML Infoset to these real-world “things,” and you map the words used to express those “things” to a particular encoding.

In messaging applications, an XmlDictionary might be used to compress serialized and encoded message size, thereby decreasing the amount of bandwidth required to transmit the message. Just as humans must agree on a vocabulary before communication is effective, both the sender and the receiver must use compatible XmlDictionary objects when exchanging messages. Internally, an XmlDictionary defines a private list of key-value pairs that can represent the element names, attribute names, and XML namespace declarations of a SOAP message.

Before we work with the XmlDictionary directly, it is necessary to examine more closely the data stored inside an instance of an XmlDictionary. The key-value pairs stored internally in an instance of an XmlDictionary are of type XmlDictionaryString. An XmlDictionaryString is simply a type that defines, among other things, a Key property that is of type Int32 and a Value property that is of type String. Even though the XmlDictionaryString type defines public constructors, an XmlDictionaryString is not typically created directly by user code, but by adding entries to a collection of XmlDictionaryString objects stored in an instance of an XmlDictionary. (We will see examples of creating an XmlDictionaryString later in this section.)

XmlDictionary defines a parameterless constructor and a seldom-used constructor that accepts an Int32 that represents the maximum number of entries in the XmlDictionaryString collection. After construction, XmlDictionaryString entries can be added to the internal XmlDictionaryString collection of the XmlDictionary by calling the Add instance method defined by the XmlDictionary type. The Add method accepts a parameter of type String and returns an instance of type XmlDictionaryString, as shown in the following code snippet:

 XmlDictionary dictionary = new XmlDictionary(); List<XmlDictionaryString> stringList = new List<XmlDictionaryString>(); // add element names to the dictionary and store in stringList stringList.Add(dictionary.Add("ReleaseDate")); stringList.Add(dictionary.Add("GoodSongs")); stringList.Add(dictionary.Add("Studio"));

Because the XmlDictionary.Add method returns an instance of an XmlDictionaryString, the dictionary local variable contains three XmlDictionaryString objects that represent “ReleaseDate”, “GoodSongs”, and “Studio”. Furthermore, the stringList local variable contains the same three XmlDictionaryString objects stored in the dictionary local variable. It is worth noting that the entries stored in the dictionary local variable are not publicly accessible-hence the need to store a list of these objects in another local variable. We can, however, see the Key and Value properties of each XmlDictionaryString by iterating over the stringList local variable as shown here:

 Console.WriteLine("entries in Collection:"); foreach (XmlDictionaryString entry in stringList) {   Console.WriteLine("Key = {0}, Value = {1}", entry.Key, entry.Value); }

When the preceding code executes, we see that a value for the Key property is automatically assigned to each XmlDictionaryString:

 entries in Collection: Key = 0, Value = ReleaseDate Key = 1, Value = GoodSongs Key = 2, Value = Studio

Notice that the value of the Key property of each XmlDictionaryString is assigned by the XmlDictionary.Add method.

An XmlDictionary is useless on its own; it must be combined with other types in the WCF XML stack to perform syntactic compression. For that, let’s turn our attention to the XmlDictionaryWriter, and then refocus our attention on how to leverage the XmlDictionaryWriter and an XmlDictionary to see how to compress a serialized and encoded XML Infoset.

The XmlDictionaryWriter Type

The XmlDictionaryWriter type is designed for Message serialization and encoding. It is derived from System.Xml.XmlWriter, and as such, it inherits many of its characteristics from the XmlWriter. Like the XmlWriter, the XmlDictionaryWriter is abstract, defines several factory methods that return instances of types derived from the XmlDictionaryWriter, wraps a System.IO.Stream, and defines many methods that begin with the word Write. In effect, using an XmlDictionaryWriter in an application is conceptually very similar to using an XmlWriter.

Unlike the XmlWriter, however, the purpose of the XmlDictionaryWriter type is to serialize and encode Message objects and optionally leverage an instance of an XmlDictionary for the purpose of syntactic compression. To this end, the XmlDictionaryWriter type defines some members that are different from the ones defined on XmlWriter. Let’s further our exploration of the XmlDictionaryWriter by examining these unique members. First we will examine the creational methods on the XmlDictionaryWriter type, and then we will see how to serialize and encode XML data to the underlying Stream.

Creating an XmlDictionaryWriter Object

The XmlDictionaryWriter defines several factory methods, and all of them accept, either directly or indirectly, a reference to a System.IO.Stream. These methods are, for the most part, overloads of the following four methods: CreateDictionaryWriter, CreateTextWriter, CreateMtomWriter, and CreateBinaryWriter.

CreateDictionaryWriter   One of the CreateDictionaryWriter factory methods on the XmlDictionaryWriter type accepts a reference to an XmlWriter. Internally, the instance returned from these methods simply wraps the XmlWriter passed as a parameter. Since the object returned from these two methods is simply a wrapper around an XmlWriter, these methods are of little value, except when an XmlDictionaryWriter is required somewhere else in the API. For example, it is possible that you need to call a method that accepts an XmlDictionaryWriter, but you have only an XmlWriter local variable. In this case, you can create an XmlDictionaryWriter from an XmlWriter by calling the CreateDictionaryWriter factory method, passing the XmlWriter as a parameter as shown here:

 MemoryStream stream = new MemoryStream(); XmlWriter xmlWriter = XmlWriter.Create(stream); XmlDictionaryWriter writer =   XmlDictionaryWriter.CreateDictionaryWriter(xmlWriter);

CreateTextWriter   The XmlDictionaryWriter type defines three CreateTextWriter factory methods. These methods return an instance of a type derived from XmlDictionaryWriter, and the purpose of this object is to generate text-encoded XML. All three of these methods accept a Stream as a parameter. Two methods accept a Stream and a System.Text.Encoding as parameters. One method accepts a Stream, an Encoding, and a Boolean as parameters. The Encoding parameter, as you might expect, dictates the Encoding used when encoding to the underlying Stream. While there are many encoding choices, only UTF-8 and Unicode (UTF-16) littleendian and big-endian are supported by the CreateTextWriter methods. If none is specified, the encoding defaults to UTF-8. The Boolean parameter specifies whether the XmlDictionaryWriter owns the underlying Stream. If this parameter is set to true, calling Close or Dispose on the XmlDictionaryWriter will call Close on the underlying Stream, thereby preventing subsequent access to the Stream. If this parameter is not specified, it defaults to true. The following code snippet shows the CreateTextWriter method in action:

 MemoryStream stream = new MemoryStream(); using (XmlDictionaryWriter writer =   XmlDictionaryWriter.CreateTextWriter(stream, Encoding.UTF8, false)) {       writer.WriteStartDocument();       writer.WriteElementString("SongName",                                 "urn:ContosoRockabilia",                                 "Aqualung");       writer.Flush(); } Console.WriteLine("XmlDictionaryWriter (Text-UTF8) wrote {0} bytes",                   stream.Position);   stream.Position = 0;   Byte[] bytes = stream.ToArray();   Console.WriteLine(BitConverter.ToString(bytes));   Console.WriteLine("data read from stream:\n{0}\n",     new StreamReader(stream).ReadToEnd());

When this code runs, it generates the following output:

 XmlDictionaryWriter (Text-UTF8) wrote 97 bytes 3C-3F-78-6D-6C-20-76-65-72-73-69-6F-6E-3D-22-31-2E-30-22-20-65-6E-63-6F-64-69-6E-67-3D-22 75-74-66-2D-38-22-3F-3E-3C-53-6F-6E-67-4E-61-6D-65-20-78-6D-6C-6E-73-3D-22-75-72-6E-3A-43 6F-6E-74-6F-73-6F-52-6F-63-6B-61-62-69-6C-69-61-22-3E-41-71-75-61-6C-75-6E-67-3C-2F-53-6F 6E-67-4E-61-6D-65-3E data read from stream: <?xml version="1.0" encoding="utf-8"?> <SongName xmlns="urn:ContosoRockabilia">Aqualung</SongName>

Notice that the XmlDictionaryWriter is wrapped in a using statement, thereby ensuring that the Dispose method is called. Also notice that the underlying Stream is available after the using block; this is possible because the Boolean parameter in the CreateTextWriter method call is set to false. It is also worth mentioning that the byte order mark (BOM) is omitted when UTF-8 is the chosen encoding. If Unicode encoding is selected, the output includes the standard UTF-16 little-endian BOM (FF FE).

CreateMtomWriter   The XmlDictionaryWriter defines two CreateMtomWriter methods. These methods return an instance of a type derived from XmlDictionaryWriter that will generate MTOM-encoded XML. Both of these methods accept a Stream as a parameter and several other parameters that control the way the XML Infoset is encoded. These parameters set the Encoding, the ContentType SOAP header, the Multipurpose Internet Mail Extensions (MIME) boundary, and the Uniform Resource Identifier (URI) for the MIME section, as well as whether the message headers are written to the underlying Stream. As with the CreateTextWriter methods, the supported encodings are UTF-8 and Unicode (UTF-16) little-endian and big-endian. The following code snippet demonstrates how to call the the CreateMtomWriter method:

 MemoryStream stream = new MemoryStream(); using (XmlDictionaryWriter writer =   XmlDictionaryWriter.CreateMtomWriter(stream, Encoding.UTF8, 1000,                                        "Application/soap+xml")) {   writer.WriteStartDocument();   writer.WriteElementString("SongName",                             "urn:ContosoRockabilia",                             "Aqualung");   writer.Flush(); } Console.WriteLine("XmlDictionaryWriter (MTOM-UTF8) wrote {0} bytes",                     stream.Position); stream.Position = 0; Byte[] bytes = stream.ToArray(); Console.WriteLine(BitConverter.ToString(bytes)); Console.WriteLine("data read from stream:\n{0}\n",     new StreamReader(stream).ReadToEnd());

When this code executes, it generates the following output. (Most of the bytes have been elided for clarity.)

 XmlDictionaryWriter (MTOM-UTF8) wrote 576 bytes 4D-49-4D-45-2D-56-65-72-73-69-6F-6E-3A-20-31-2E-30-0D-0A-43-6F-6E-74-65-6E-74-2D-54-79-70 65-3A-20-6D-75-6C-74-69-70-61-72-74-2F-72-65-6C-61-74-65-64-3B-74-79-70-65-3D-22-61-70-70 6C-69-63-61-74-69-6F-6E-2F-78-6F-70-2B-78-6D-6C-22-3B-62-6F-75-6E-64-61-72-79-3D-22-37-31 65-37-62-35-32-61-2D-37-61-34-36-2D-34-37-32-36-2D-62-61-62-64-2D-31-37-37-32-32-39-65-32 38-66-30-33-2B-69-64-3D-31-22-3B-73-74-61-72-74-3D-22-3C-68-74-74-70-3A-2F-2F-74-65-6D-70 75-72-69-2E-6F-72-67-2F-30-2F-36-33-32-38-37-31-37-34-35-30-37-30-38-39-31 data read from stream: MIME-Version: 1.0 Content-Type: multipart/related;   type="application/xop+xml";   boundary="+id=1";   start="<http://tempuri.org/0/632871745070891488>";   start-info="Application/soap+xml" --+id=1 Content-ID: <http://tempuri.org/0/632871745070891488> Content-Transfer-Encoding: 8bit Content-Type: application/xop+xml; charset=utf-8;    type="Application/soap+xml" <?xml version="1.0" encoding="utf-8"?>   <SongName xmlns="urn:ContosoRockabilia">     Aqualung   </SongName> --+id=1–

The following code snippet demonstrates that calling the other CreateMtomWriter method produces very different output:

 MemoryStream stream = new MemoryStream(); using (XmlDictionaryWriter writer =   XmlDictionaryWriter.CreateMtomWriter(stream,                                        Encoding.UTF8,                                        1000,                                        "startInfo",                                        "boundary",                                        "urn:startUri",                                        false,                                        false)){       writer.WriteStartDocument();       writer.WriteElementString("SongName",                                 "urn:ContosoRockabilia",                                 "Aqualung");       writer.Flush();   } Console.WriteLine("XmlDictionaryWriter (MTOM-UTF8) wrote {0} bytes",     stream.Position); stream.Position = 0; Byte[] bytes = stream.ToArray(); Console.WriteLine(BitConverter.ToString(bytes)); Console.WriteLine("data read from stream:\n{0}\n",   new StreamReader(stream).ReadToEnd());

When this code runs, it produces the following output. (Most of the bytes have been omitted for clarity.)

 XmlDictionaryWriter (MTOM-UTF8) wrote 256 bytes 0D-0A-2D-2D-62-6F-75-6E-64-61-72-79-0D-0A-43-6F-6E-74-65-6E-74-2D-49-44-3A-20-3C-75-72-6E 3A-73-74-61-72-74-55-72-69-3E-0D-0A-43-6F-6E-74-65-6E-74-2D-54-72-61-6E-73-66-65-72-2D-45 6E-63-6F-64-69-6E-67-3A-20-38-62-69-74-0D-0A data read from stream: --boundary Content-ID: <urn:startUri> Content-Transfer-Encoding: 8bit Content-Type: application/xop+xml;charset=utf-8;type="startInfo" <?xml version="1.0" encoding="utf-8"?> <SongName xmlns="urn:ContosoRockabilia">   Aqualung </SongName> --boundary–

Notice that the parameters of the second CreateMtomWriter method map to different locations in the MTOM-encoded data. Notice also that setting the penultimate parameter to false removes the multipart message headers at the beginning of the Stream.

Extreme care must be taken when calling the aforementioned CreateMtomWriter method. While both of the CreateMtomWriter methods serialize XML Infosets and encode them in an MTOM-compliant manner, the second method offers more control over the encoded data. Clearly, the second method has benefits–namely, it allows more control over the formatting of the data. Certain applications might need this level of control. This control, however, introduces the possibility of breaking interoperability if the receiving application cannot interpret the information. As you saw in Chapter 2, one of the main motivators for MTOM is interoperability, so using the method might, if it is used incorrectly, subvert the very reason to use the MTOM encoding in the first place.

CreateBinaryWriter   The XmlDictionaryWriter type also defines four CreateBinaryWriter methods. These methods return an instance of a type derived from the XmlDictionaryWriter that generates binary-encoded XML. All of these methods accept a Stream as a parameter. Three of the methods accept an XmlDictionary, two of the methods also accept an XmlBinaryWriterSession, and one also accepts a Boolean. If specified, the XmlDictionary parameter indicates the XmlDictionary object used for syntactic compression. If no compression is required in an application, null can be passed for this parameter. In a manner consistent with the CreateTextWriter methods, the Boolean parameter in the CreateBinaryWriter method indicates whether the XmlDictionaryWriter owns the underlying Stream.

The XmlBinaryWriterSession parameter on the CreateBinaryWriter method allows the sender and receiver to automatically create and coordinate a dynamic XmlDictionary. As previously mentioned, the key-value pairs must be added to an XmlDictionary object before it is used, and the contents of the XmlDictionary must be shared among messaging participants (typically in an out-of-band mechanism). Sharing the contents of an XmlDictionary among messaging participants can be quite a challenge, and the XmlBinaryWriterSession addresses these challenges. The XmlBinaryWriterSession type emits the key-value pairs at the beginning of the Stream, thereby eliminating the need to explicity share an XmlDictionary. Internally, the XmlBinaryWriterSession maintains its own XmlDictionary and adds XmlDictionaryString objects as element names, attribute names, and XML namespaces appear in the content that is to be serialized. The XmlBinaryWriterSession generates data that is not as compact as data serialized via an equivalent XmlDictionary and a binary encoding, but the XmlBinaryWriterSession does not force us to know the contents of the XmlDictionary ahead of time or coordinate the XmlDictionary manually with the receiver. To decode the data in the underlying Stream at the receiving end of a message exchange, the receiver must use an XmlBinaryReaderSession object. The XmlBinaryReaderSession populates itself automatically from the dictionary emitted in the first part of the Stream. In effect, the XmlBinaryWriterSession type creates and coordinates an XmlDictionary dynamically, but does so with a performance cost.

Note 

Notice that this is the first mention of an XmlDictionary in the entire XmlDictionaryWriter type. As it turns out, binary-encoded XML is the only logical place to perform syntactical compression. All of the other factory methods are designed to generate some form of text. By their very nature, the UTF-8 and UTF-16 text encodings are well defined and do not lend themselves to compression in the same way that binary encodings do. There are other well-defined mechanisms for compressing text data (GZIP, the Huffman algorithm, and so on). It is also interesting that the XmlDictionaryWriter type is capable of a varied set of encodings, yet was named for one capability that is available only in the binary encoding.

The following code snippet shows how to call the CreateBinaryWriter method without using an XmlDictionary. (You will see how to leverage an XmlDictionary later in this chapter.)

 MemoryStream stream = new MemoryStream(); using (XmlDictionaryWriter writer =     XmlDictionaryWriter.CreateBinaryWriter(stream, null, null)) {   writer.WriteStartDocument();   writer.WriteElementString("SongName",                             "urn:ContosoRockabilia",                             "Aqualung");   writer.Flush();   Console.WriteLine(     "XmlDictionaryWriter (Binary, no dictionary) wrote {0} bytes",     stream.Position);   stream.Position = 0;   Byte[] bytes = stream.ToArray();   Console.WriteLine(BitConverter.ToString(bytes)); }

When this code executes, it produces the following output:

 XmlDictionaryWriter (Binary, no dictionary) wrote 43 bytes 3F-08-53-6F-6E-67-4E-61-6D-65-04-15-75-72-6E-3A-43-6F-6E-74-6F-73-6F-52-6F-63-6B-61-62-69 6C-69-61-A1-08-41-71-75-61-6C-75-6E-67

Notice that the binary encoder generates output that is an order of magnitude smaller than the output of the MTOM encoder and half the size of the output of the text encoder. Also notice that access to the stream local variable is inside the block of a using statement. By default, the CreateBinaryWriter method puts the resultant XmlDictionaryWriter in control of the underlying Stream.

The Write Methods

Now that we have seen the different ways to create an XmlDictionaryWriter object, let’s examine how to use this object to write XML. As previously mentioned, the XmlDictionaryWriter defines many methods for the purpose of writing XML to the underlying Stream, and all of these method names start with Write. Generally speaking, writing XML with an XmlDictionaryWriter is very similar to writing XML with the XmlWriter. The XmlDictionaryWriter does, however, define several unique methods that complement the needs of a messaging application. To prevent the risk of repeating documentation, this chapter does not elucidate the XmlDictionaryWriter methods that mimic the characteristics of the XmlWriter and instead focuses on a feature that is unique to the XmlDictionaryWriter: the ability to leverage the XmlDictionary.

Writing with an XmlDictionary

Many of the Write methods on the XmlDictionaryWriter type contain parameters of type XmlDictionaryString. These methods are typically paired with similar methods that accept parameters of type String. Consider the following method prototypes available in XmlDictionaryWriter:

 // method accepting String objects public void WriteElementString(String localName,                                String ns,                                String value); // method accepting XmlDictionaryString and String objects public void WriteElementString(XmlDictionaryString localName,                                XmlDictionaryString namespaceUri,                                String value);

Notice that both of these methods contain three parameters and that the second method simply accepts two XmlDictionaryString parameters for local name and namespace. It is important to note that the first method is defined on the XmlWriter type and the second method is defined on the XmlDictionaryWriter type. Given this tuple, you might wonder how they differ. For the answer, let’s test both methods and then compare the results. The following code snippet uses the WriteElementString method that accepts three String parameters:

 private static void UseTextWriter() {   MemoryStream stream = new MemoryStream();   using (XmlDictionaryWriter writer =       XmlDictionaryWriter.CreateTextWriter(stream, Encoding.UTF8, true)) {     writer.WriteElementString("SongName",                               "urn:ContosoRockabilia",                               "Aqualung");     writer.Flush();     Console.WriteLine("XmlDictionaryWriter (Text-UTF8) wrote {0} bytes",                       stream.Position);     stream.Position = 0;     Byte[] bytes = stream.ToArray();     Console.WriteLine(BitConverter.ToString(bytes));     Console.WriteLine("data read from stream:\n{0}\n",       new StreamReader(stream).ReadToEnd());   } }

This code generates the following output when it runs:

 XmlDictionaryWriter (Text-UTF8) wrote 59 bytes 3C-53-6F-6E-67-4E-61-6D-65-20-78-6D-6C-6E-73-3D-22-75-72-6E-3A-43-6F-6E-74-6F-73-6F-52-6F 63-6B-61-62-69-6C-69-61-22-3E-41-71-75-61-6C-75-6E-67-3C-2F-53-6F-6E-67-4E-61-6D-65-3E data read from stream:    <SongName xmlns="urn:ContosoRockabilia">Aqualung</SongName>

Next let’s run a similar code snippet, but this time, call the WriteElementString method that accepts XmlDictionaryString parameters:

 private static void UseTextWriterWithDictionary() {   MemoryStream stream = new MemoryStream();   // build the dictionary and populate   XmlDictionary dictionary = new XmlDictionary();   List<XmlDictionaryString> stringList = new List<XmlDictionaryString>();   stringList.Add(dictionary.Add("SongName"));   stringList.Add(dictionary.Add("urn:ContosoRockabilia"));   using (XmlDictionaryWriter writer =       XmlDictionaryWriter.CreateTextWriter(stream, Encoding.UTF8, true)) {     writer.WriteElementString(stringList[0], stringList[1], "Aqualung");     writer.Flush();     Console.WriteLine("XmlDictionaryWriter (Text-UTF8) wrote {0} bytes",                       stream.Position);     stream.Position = 0;     Byte[] bytes = stream.ToArray();     Console.WriteLine(BitConverter.ToString(bytes));     Console.WriteLine("data read from stream:\n{0}\n",                       new StreamReader(stream).ReadToEnd());   } }

This code generates the following output:

 XmlDictionaryWriter (Text-UTF8) wrote 59 bytes 3C-53-6F-6E-67-4E-61-6D-65-20-78-6D-6C-6E-73-3D-22-75-72-6E-3A-43-6F-6E-74-6F-73-6F-52-6F 63-6B-61-62-69-6C-69-61-22-3E-41-71-75-61-6C-75-6E-67-3C-2F-53-6F-6E-67-4E-61-6D-65-3E data read from stream: <SongName xmlns="urn:ContosoRockabilia">Aqualung</SongName>

Both methods generate the same output! The syntactical compression that we expect when using an XmlDictionary did not occur. As stated in the discussion of XmlDictionaryWriter factory methods, the XmlDictionary is useful only when the XmlDictionaryWriter is going to generate binary-encoded XML. However, the ability to use an XmlDictionary is not limited to XmlDictionaryWriter methods that generate binary-encoded XML. This characteristic is intentional. To see why, consider the following method:

 // assume that stringList contains XmlDictionaryString objects // and is populated before this method is called private static void WriteSomeXml(XmlDictionaryWriter writer) {   writer.WriteElementString(stringList[0], stringList[1], "Aqualung"); }

The WriteSomeXml method will accept any parameter that derives from the XmlDictionaryWriter type. This includes an XmlDictionaryWriter that produces binary-encoded XML, as well as one that produces text-encoded XML. As a result of the encoding flexibility of the XmlDictionaryWriter type, the WriteSomeXml method can be used to write XML that adheres to a wide variety of encodings. In other words, the inclusion of the WriteElementString overload that accepts parameters of type XmlDictionaryString in all concrete XmlDictionaryWriter types results in a more flexible API.

If we create an XmlDictionaryWriter by calling the CreateBinaryWriter factory method and then call a Write method, we see a very different set of data in the underlying Stream. The following code snippet demonstrates:

   // create the dictionary and add dictionary strings   XmlDictionary dictionary = new XmlDictionary();   List<XmlDictionaryString> stringList = new List<XmlDictionaryString>();   stringList.Add(dictionary.Add("SongName"));   stringList.Add(dictionary.Add("urn:ContosoRockabilia")); MemoryStream stream = new MemoryStream(); using (XmlDictionaryWriter writer =     XmlDictionaryWriter.CreateBinaryWriter(stream, dictionary, null)) {   // write using the dictionary - element name, namespace, value   writer.WriteElementString(stringList[0], stringList[1], "Aqualung");   writer.Flush();   Console.WriteLine("Using XmlDictionary w/Binary , wrote {0} bytes",                     stream.Position);   stream.Position = 0;   Byte[] bytes = stream.ToArray();   Console.WriteLine(BitConverter.ToString(bytes)); }

When this code runs, the following output is generated:

 Using XmlDictionary w/Binary, wrote 14 bytes 42-00-0A-02-99-08-41-71-75-61-6C-75-6E-67

Notice that the combination of an XmlDictionary with binary-encoded XML results in over a 75 percent reduction in the size of the data produced with the text encoding (14 bytes vs. 59 bytes). The substitution of the XmlDictionaryString integer keys for the string values in the underlying Stream provides this compression. Keep in mind that the preceding code snippet substitutes the text of both the element name (SongName) and the namespace (urn:ContosoRockabilia). To further emphasize this point, the following code snippet shows how to generate binary-encoded XML without the assistance of an XmlDictionary:

 private static void UseBinaryWriter() {   MemoryStream stream = new MemoryStream();   using (XmlDictionaryWriter writer =       XmlDictionaryWriter.CreateBinaryWriter(stream, null, null)) {     writer.WriteElementString("SongName",                               "urn:ContosoRockabilia",                               "Aqualung");     writer.Flush();     Console.WriteLine("Not Using XmlDictionary w/Binary, wrote {0} bytes",                       stream.Position);     stream.Position = 0;     Byte[] bytes = stream.ToArray();     Console.WriteLine(BitConverter.ToString(bytes));   } }

When this code executes, it generates the following output:

 Not Using XmlDictionary w/Binary, wrote 43 bytes 3F-08-53-6F-6E-67-4E-61-6D-65-04-15-75-72-6E-3A-43-6F-6E-74-6F-73-6F-52-6F-63-6B-61-62-69 6C-69-61-A1-08-41-71-75-61-6C-75-6E-67

In our test, the combination of an XmlDictionary and binary-encoded XML resulted in a 67 percent reduction in data size when compared with using binary-encoded XML with no XmlDictionary. To further understand the XmlDictionary and its purpose with the XmlDictionaryWriter, let’s take another look at the byte sequences generated when the text encoder, binary encoder with no dictionary, and binary encoder with dictionary are used:

 XML to be encoded: <SongName xmlns="urn:ContosoRockabilia">Aqualung</SongName> XmlDictionaryWriter (Text-UTF8) wrote 59 bytes 3C-53-6F-6E-67-4E-61-6D-65-20-78-6D-6C-6E-73-3D-22-75-72-6E-3A-43-6F-6E-74-6F-73-6F-52-6F 63-6B-61-62-69-6C-69-61-22-3E-41-71-75-61-6C-75-6E-67-3C-2F-53-6F-6E-67-4E-61-6D-65-3E XmlDictionaryWriter (binary) No XmlDictionary wrote 43 bytes 3F-08-53-6F-6E-67-4E-61-6D-65-04-15-75-72-6E-3A-43-6F-6E-74-6F-73-6F-52-6F-63-6B-61-62-69 6C-69-61-A1-08-41-71-75-61-6C-75-6E-67 XmlDictionaryWriter (binary), With XmlDictionary wrote 14 bytes 41-00-06-02-A1-08-41-71-75-61-6C-75-6E-67 

Notice the bold byte sequences. If we translate these byte sequences to ASCII characters, we see the following ASCII-to-byte mapping:

 SongName 53-6F-6E-67-4E-61-6D-65 urn:ContosoRockabilia 75-72-6E-3A-43-6F-6E-74-6F-73-6F-52-6F-63-6B-61-62-69-6C-69-61 Aqualung 41-71-75-61-6C-75-6E-67

As evidenced by the preceding examples, an XmlDictionaryWriter that generates binaryencoded XML but does not use an XmlDictionary writes the element names, XML namespaces, attribute values, and element values directly to the underlying Stream. Likewise, an XmlDictionaryWriter that generates binary-encoded XML with the assistance of an XmlDictionary directly writes the element and attribute values, but substitutes single bytes for the element names and XML namespaces in the underlying Stream.

Note 

In my view, this sort of compression is a huge benefit. In distributed computing, one aspect of performance is the size of the transmitted data. In general, smaller data transmissions result in more highly performing applications. To relate this directly to messaging applications, smaller messages imply smaller data transmisstions, which in turn, imply more highly performing applications. Typically, developers and architects are so used to thinking about text-encoded XML that they assume that SOAP messages have a large footprint on the wire and perform poorly as a result. With WCF, this assumption is simply not true, because WCF can generate very compact XML. It is important to note, however, that the binary encoding discussed here does not interoperate with other platforms. Over time, I expect the industry to adopt standard binary encodings.

Now that we have seen how to instantiate an XmlDictionaryWriter and use it to write XML to a Stream, let’s take a look at how to read encoded XML from a Stream using the XmlDictionaryReader.

The XmlDictionaryReader Type

The XmlDictionaryReader abstract type derives from System.Xml.XmlReader, and as such, inherits many of its characteristics from XmlReader. Like the XmlReader, the XmlDictionaryReader type defines several factory methods that return instances of types derived from XmlDictionaryReader. Furthermore, the XmlDictionaryReader wraps a Stream and defines many methods that begin with the word Read. As a result of its derivation hierarchy, using an XmlDictionaryReader is very similar to using an XmlReader.

Unlike the XmlReader, the purpose of the XmlDictionaryReader type is to read serialized and encoded XML Infosets and optionally leverage an instance of an XmlDictionary for the purpose of reversing syntactic compression. In effect, the XmlDictionaryReader is the converse of the XmlDictionaryWriter, and the object models of these two types are similar. Let’s start our exploration of the XmlDictionaryReader by examining its creational methods and then examine how to use the Read methods. Because of the similarities between the XmlDictionaryReader and the XmlDictionaryWriter, this section will be shorter than the section on the XmlDictionaryWriter type.

Creating an XmlDictionaryReader Object

The XmlDictionaryReader type defines several factory methods, and all of them accept, either directly or indirectly, a reference to a Stream or a Byte[]. In general, the stream-oriented methods are similar to the buffer-oriented methods. For the most part, all of these factory methods are overloads of the four methods CreateDictionaryReader, CreateTextReader, CreateMtomReader, and CreateBinaryReader, and they mirror the behavior of the similarly named XmlDictionaryWriter factory methods. To keep repetition to a minimum, we will focus on the traits of the factory methods that are unique to the XmlDictionaryReader.

Several of the factory methods accept a reference to a Stream. Other parameters used in these stream-oriented factory methods include a reference to an XmlDictionaryQuotas object and a reference to an OnXmlDictionaryReaderClose delegate. In all cases, the former calls the latter, passing null for the XmlDictionaryQuotas and OnXmlDictionaryReaderClose parameters.

The XmlDictionaryQuotas type is a state container that describes the maximum values for important thresholds related to XML deserialization. For example, this type defines several properties that signify the maximum node depth to deserialize, maximum String length of a deserialized Message, maximum Array length of the body, and so on.

The OnXmlDictionaryReaderClose delegate is invoked near the end of the Close method implementation on the XmlDictionaryReader type. By the time this delegate is invoked, most of the state of the XmlDictionaryReader has been set to null. As a result, this delegate can be used as a notification mechanism (much like an event), but it cannot provide any valuable information about the state of the XmlDictionaryReader (unless of course, null is valuable). Message encoders use the OnXmlDictionaryReaderClose delegate to pool XmlDictionaryReader objects. These encoders rely on the OnXmlDictionaryReaderClose delegate as a notification that returns an instance of the XmlDictionaryReader to the resource pool.

The following code snippet illustrates how to instantiate an XmlDictionaryReader:

 private static void CreateTextReader() {   Console.WriteLine("==== Creating XML Dictionary Text Reader ====");   MemoryStream stream = new MemoryStream();   // create an XmlDictionaryWriter and serialize/encode some XML   XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter(stream,     Encoding.BigEndianUnicode, false);   writer.WriteStartDocument();     writer.WriteElementString("SongName",                               "urn:ContosoRockabilia",                               "Aqualung");   writer.Flush();   stream.Position = 0;   // create an XmlDictionaryReader to decode/deserialize the XML   XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(     stream, Encoding.BigEndianUnicode, new XmlDictionaryReaderQuotas(),     delegate { Console.WriteLine("closing reader"); } );   reader.MoveToContent();   Console.WriteLine("Read XML Content:{0}",reader.ReadOuterXml());   Console.WriteLine("about to call reader.Close()");   reader.Close();   Console.WriteLine("reader closed"); }

When the preceding code snippet runs, the following output is generated:

 ==== Creating XML Dictionary Text Reader ==== Read XML Content: <SongName xmlns="urn:ContosoRockabilia">Aqualung</SongName> about to call reader.Close() closing reader reader closed

It is important to note that the other factory methods on the XmlDictionaryReader accept parameters that map very closely to the factory methods defined on the XmlDictionaryWriter type. These parameters have the same function as they do in the XmlDictionaryWriter type.

Round-Tripping XML with an XmlDictionary

Now that you’ve seen how to instantiate both the XmlDictionaryWriter and the XmlDictionaryReader, let’s examine how to read binary-encoded XML with an XmlDictionary. As shown in the following code snippet, this is similar to what you’ve seen with the XmlDictionaryWriter:

 MemoryStream stream = new MemoryStream(); // create the dictionary and add dictionary strings XmlDictionary dictionary = new XmlDictionary(); List<XmlDictionaryString> stringList = new List<XmlDictionaryString>(); stringList.Add(dictionary.Add("SongName")); stringList.Add(dictionary.Add("urn:ContosoRockabilia")); // use an XmlDictionaryWriter to serialize some XML using (XmlDictionaryWriter writer =     XmlDictionaryWriter.CreateBinaryWriter(stream, dictionary, null)) {   // write using the dictionary - element name, namespace, value   writer.WriteElementString(stringList[0], stringList[1], "Aqualung");   writer.Flush();   Console.WriteLine("Using Dictionary, wrote {0} bytes",                     stream.Position);   stream.Position = 0;   Byte[] bytes = stream.ToArray();   Console.WriteLine(BitConverter.ToString(bytes));   // create an XmlDictionaryReader passing the Stream   // and an XmlDictionary   XmlDictionaryReader reader =     XmlDictionaryReader.CreateBinaryReader(stream, dictionary, new       XmlDictionaryReaderQuotas());   reader.Read();   Console.WriteLine("data read from stream:\n{0}\n",     reader.ReadOuterXml()); }

When this code executes, the following output is generated:

 XmlDictionaryWriter (Binary w/dictionary) wrote 14 bytes 42-00-0A-02-99-08-41-71-75-61-6C-75-6E-67 data read from stream: <SongName xmlns="urn:ContosoRockabilia">Aqualung</SongName>

Notice that the same XmlDictionary passed to the CreateBinaryWriter method on the XmlDictionaryWriter is also passed to the CreateBinaryReader method on the XmlDictionaryReader. Admittedly, passing a reference to the same XmlDictionary object is a crude way to ensure that both the XmlDictionaryWriter and the XmlDictionaryReader are using the same vocabulary, but nonetheless, it illustrates that the XmlDictionaryReader is able to interpret the compression performed by an XmlDictionaryWriter and an XmlDictionary.




Inside Windows Communication Foundation
Inside Windows Communication Foundation (Pro Developer)
ISBN: 0735623066
EAN: 2147483647
Year: 2007
Pages: 106
Authors: Justin Smith

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