As you saw in Chapter 2, header blocks are used by SOAP message processing infrastructures to, among other things, express addressing, routing, and security information. Since WCF is fundamentally a message processing infrastructure that fully supports SOAP, it has several facilities for creating, serializing, deserializing, and interrogating the header blocks of a SOAP message. Remembering that the Message type is a common language runtime abstraction of a SOAP message, it follows that the Message type defines members that allow the WCF infrastructure to work with the header blocks of an outgoing or a received Message. The aptly named Headers instance property of the Message type provides this capability. As with other key types in WCF, working with the Headers property requires us to interact with other types in the WCF API-namely, the MessageHeader, the MessageHeaders, and the EndpointAddress types. The names of these types gives hints of their purpose. For example, the MessageHeader type is a generalized common language runtime abstraction of a SOAP header block; the MessageHeaders type is, in a broad sense, a grouping of MessageHeader objects; and the EndpointAddress type is a common language runtime abstraction of a WS-Addressing endpoint reference. When used in concert, these types provide the ability to insert header blocks to a Message, serialize and encode the contents of those header blocks, deserialize and decode the header blocks of a received Message, and extract information from deserialized header blocks. In this section, we will examine these fundamental types and how they can be used with the Message type.
The fundamental building block for SOAP message header blocks in WCF is the MessageHeader type, and its object model is very similar to that of the Message type. Like the Message type, the MessageHeader type is an abstract class that exposes several factory methods that each return a new instance of a concrete MessageHeader derived type. The MessageHeader type also defines several methods for serializing the contents of a MessageHeader via an XmlWriter or an XmlDictionaryWriter.
There are several CreateHeader factory methods defined on the MessageHeader type. Each of these factory methods accept different combinations of parameters, but three parameters signifying the name (String), namespace (String), and value (Object) of the header block are always present. The remaining parameters allow us to pass a custom serializer, as well as values for the mustUnderstand, actor, and relay SOAP header block attributes. The following code snippet demonstrates how to build a simple MessageHeader object that signifies the MessageID header block as defined in WS-Addressing:
String WSAddNS = "http://www.w3.org/2005/08/addressing"; MessageHeader header = MessageHeader.CreateHeader("MessageID", WSAddNS, new UniqueId().ToString()); Console.WriteLine(header.ToString());
The following output is generated when this code executes:
<MessageID xmlns="http://www.w3.org/2005/08/addressing"> urn:uuid: </MessageID>
Notice that the XML namespace and the name of the MessageID information item must be known to create a MessageHeader object that serializes to (or in this case, renders as a String) the WS-Addressing MessageID header block. I’m not sure about you, but I would rather not memorize the gaggle of namespaces and header block names defined in all of the WS-* specifications. The WCF architects felt the same way, and they have provided several mechanisms that create WS-*-compliant header blocks for us. We will look at these mechanisms at different points throughout this book, as well as in the section “The MessageHeaders Type” later in this chapter.
It is important to note that we can also build MessageHeader objects that represent custom header blocks not related to WS-*. For example, a purchase order processing application might need to add a header block named PurchaseOrderInfo to a Message before the Message is sent to another messaging participant. To do this, we simply change the XML namespace, header block name, and header block value from the preceding example to fit the needs of the application. An example of a custom MessageHeader is shown here:
MessageHeader header = MessageHeader.CreateHeader("PurchaseOrderDate", "http://wintellect.com/POInfo", DateTime.Now); Console.WriteLine(header.ToString());
This code generates the following output:
<PurchaseOrderDate xmlns="http://wintellect.com/POInfo"> 2007-01-12T09:18:52.020824-04:00 </PurchaseOrderDate>
Note | As you’ll see in Chapter 9, the WCF infrastructure can do this work for us through the use of a message contract. When we take this easier and less-error-prone approach, the WCF infrastructure is executing code that is fundamentally similar to the preceding code snippet. It is also important to point out that a MessageHeader object is of little value on its own. To have any meaning, we need to reference that MessageHeader object from a Message object. You’ll learn more about adding a MessageHeader to a Message in the section “The MessageHeaders Type” later in this chapter. |
The MessageHeader type defines several members that serialize and encode the state of a MessageHeader object. Like the Message type, many of these members are methods that start with the word Write and accept either an XmlWriter or an XmlDictionaryWriter as a parameter. The MessageHeader type also defines the OnWriteHeaderContents protected abstract method and the OnWriteStartHeader protected virtual method to allow types derived from MessageHeader to exert more control over MessageHeader serialization. In a manner befitting an extensible framework, the implementation of the Write methods in the MessageHeader type calls the appropriate protected methods, thereby passing the task of serialization to the derived type.
Note | It is hard for me to imagine a reason to serialize a MessageHeader object outside the greater context of Message serialization. To put it another way, the only time you will need to care about MessageHeader serialization is when you are serializing a Message. Since the Write methods defined on the Message type serialize only the SOAP envelope and the SOAP body of a Message, it is necessary to serialize MessageHeader objects when serializing a Message object. We will revisit this topic in the section “The MessageHeaders Type” later in this chapter. |
The following code snippet illustrates how to call one of the Write methods to serialize a MessageHeader object via an XmlDictionaryWriter:
[Serializable] sealed class PurchaseOrderInfo { internal Int32 PONumber; internal DateTime? IssueDate; internal Double? Amount; internal PurchaseOrderInfo(Int32 ponumber, DateTime? issueDate, Double? amount){ PONumber = ponumber; IssueDate = issueDate; Amount = amount; } } class Program { static void Main(){ // create an object to store in the MessageHeader PurchaseOrderInfo poinfo = new PurchaseOrderInfo(1000, DateTime.Now, 10.92); // create the MessageHeader MessageHeader header = MessageHeader.CreateHeader( "PurchaseOrderInfo", "http://wintellect.com/POInfo", poinfo); MemoryStream stream = new MemoryStream(); XmlDictionaryWriter writer = XmlDictionaryWriter.CreateTextWriter( stream, Encoding.UTF8, false); // Serialize the MessageHeader via an XmlDictionaryWriter header.WriteHeader(writer, MessageVersion.Soap12WSAddressing10); writer.Flush(); stream.Position = 0; // Show the contents of the Stream Console.WriteLine(new StreamReader(stream).ReadToEnd()); } }
When this code executes, the following output is generated:
<PurchaseOrderInfo xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://wintellect.com/POInfo"> <Amount xmlns="http://schemas.datacontract.org/2004/07/"> 10.92 </Amount> <IssueDate xmlns="http://schemas.datacontract.org/2004/07/"> 2007-01-11T15:06:25.515625-04:00 </IssueDate> <PONumber xmlns="http://schemas.datacontract.org/2004/07/"> 1000 </PONumber> </PurchaseOrderInfo>
Notice that the output contains information items that are subordinate to the PurchaseOrderInfo information item. Nesting information items as shown here is a byproduct of the way the PurchaseOrderInfo object serializes, rather than a direct function of the MessageHeader object. Why, you might ask, do we care about nested information items in a serialized header block? We care because many of the header blocks defined in WS-* and many custom headers are structured as nested information items. In a nutshell, if we need to create nested information items when a MessageHeader is serialized, we must either pass an object to the MessageHeader factory method that serializes appropriately or subclass the MessageHeader type and control serialization through the implementation. Subclassing the MessageHeader type offers more control than relying on the default serializer in WCF and is certainly easier that writing our own serializer. As a result, the WCF API internally uses subclassed MessageHeader types as a means to serialize WS-* header blocks.
As you saw in Chapter 2, WS-Addressing identifies and standardizes constructs used to address SOAP messages, and one of these core constructs is the endpoint reference. As it is defined in WS-Addressing, an endpoint reference has a general structure similar to the one shown here (and also shown in Chapter 2):
<wsa:EndpointReference xmlns:wsa="..." xmlns:wnt="..."> <wsa:Address>http://wintellect.com/OrderStuff</wsa:Address> <wsa:ReferenceParameters> <wnt:OrderID>9876543</wnt:OrderID> <wnt:ShoppingCart>123456</wnt:ShoppingCart> </wsa:ReferenceParameters> </wsa:EndpointReference>
The information items shown here are really just one header block in a SOAP message. Keeping in mind that the MessageHeader type is a common language runtime abstraction of a SOAP header block, we can assume that a MessageHeader object can be the common language runtime abstraction of an endpoint reference. Notice from the preceding structure that the reference parameters information item is subordinate to the endpoint reference as a whole, and as mentioned in the previous section, this presents some interesting serialization challenges.
If we try to build a MessageHeader object that will serialize to a full endpoint reference (that is, address and reference parameter information items), we have three options:
Define a type that represents an endpoint reference and pass an instance of that type as the Object parameter in a CreateHeader factory method.
Subclass MessageHeader in such a way that we can customize serialization.
Define a type that represents an endpoint reference and subclass the MessageHeader type.
Upon trying the first option, we quickly find that it is, by itself, unworkable (you’ll learn more about serialization in Chapter 9), thereby forcing us to take the second or third approach. We can make the second approach work, but if we refactor our design, we quickly see that other parts of our application need a type that represents an endpoint reference. In other words, we are presented with a situation that lends itself to defining a type that represents an endpoint reference. Due to these facts, the WCF team took the third approach. They defined the EndpointAddress type as a way to represent a WS-Addressing endpoint reference and subclassed the MessageHeader type. It is through this combination that we can represent an endpoint reference with a MessageHeader object and serialize it properly. You’ll see this in more detail in the section “The MessageHeaders Type” on the next page.
Several other facets of the MessageHeader type are worth mentioning. The most striking aspect of the MessageHeader type is the lack of a way to extract the value of a MessageHeader after it is instantiated. At first glance, this appears to present a real problem, especially when we try to interrogate the header block contents of a deserialized SOAP message. The Headers property of the Message type provides us with the solution to this dilemma. The Headers property is of type MessageHeaders, and this type defines mechanisms to extract the contents of all MessageHeader objects present in the Message. We will examine this topic in more detail in the section “The MessageHeaders Type” on the next page.
Another curious member of the MessageHeader type is the IsReferenceParameter read-only property. Useful when interrogating the header blocks of a deserialized SOAP message, this property indicates whether a MessageHeader object is a WS-Addressing reference parameter or reference property. You might be saying to yourself, “Didn’t you just say that a reference parameter/property is, in effect, part of a MessageHeader object that represents an endpoint reference?” Yes, I did, but that does not subjugate the need to know if a MessageHeader object is a reference parameter or reference property.
Consider the structure of the To message information item in a SOAP message, as shown here:
<S:Envelope xmlns:S="..." xmlns:wsa="..." xmlns:wnt="... "> <S:Header> ... <wsa:To>http://wintellect.com/OrderStuff</wsa:To> <wnt:OrderID wsa:IsReferenceParameter="true">9876543</wnt:OrderID> <wnt:ShoppingCart wsa:IsReferenceParameter="true"> 123456 </wnt:ShoppingCart> ... </S:Header> <S:Body>
As illustrated here, the OrderID and ShoppingCart information items are their own header blocks and represent the reference parameters of an endpoint reference. Combined with the To URI, they can be used to create an endpoint reference, and therefore, they are different from other application-specific header blocks. We can easily build MessageHeader objects that represent the OrderID and ShoppingCart information items of the logical To endpoint reference, but it is not quite as easy to distinguish those MessageHeader objects from other MessageHeader objects unless the IsReferenceParameter attribute is present. In other words, when we deserialize a SOAP message into a Message object and interrogate the MessageHeader objects, we can determine whether any of these objects are reference parameters by checking the value of the IsReferenceParameter property. Once we have determined which header blocks are reference parameters, we can combine them with the To URI, thereby effectively building a To endpoint reference. You’ll learn more about this topic in the next section.
Because a SOAP message is likely to contain many header blocks, we need a way to represent a group of MessageHeader objects in a Message. The MessageHeaders type serves this purpose, and the Message type defines a read-only instance property named Headers that is of type MessageHeaders. The Headers property is the primary way that we add, modify, interrogate, or remove a MessageHeader from an instance of a Message. In one sense, this section covers the MessageHeaders type, and virtually all of the information can be applied to the Headers property of the Message type. In contrast to the body of a Message, we are free to modify the contents of the Headers property after we instantiate a Message. The MessageHeaders type is a concrete class that defines no factory methods. This is worthy of note since many of the other types discussed in this chapter are abstract and define factory methods.
As previously mentioned, the MessageHeaders type is, on one level, a grouping of MessageHeader objects. The object model of the MessageHeaders type, however, is curiously missing a member that returns a collection of MessageHeader objects. Instead, the MessageHeaders type implements the IEnumerable<MessageHeaderInfo> and IEnumerable interfaces. This means that we can simply iterate over the MessageHeaders type to see all of the header blocks (after the MessageHeaders object has been populated).
Note | For thoroughness, I have to mention that the MessageHeaderInfo type is the base type of MessageHeader. The MessageHeaderInfo type defines several properties representing SOAP header block attributes like Actor, MustUnderstand, and so on. Quite frankly, I see little reason for the existence of this type since the MessageHeader type is abstract. |
The MessageHeaders type defines three publicly visible constructors. It is important to note that most developers will never use these constructors directly, because the existing infrastructure in the Message type (or its derived types) will call one of these constructors for you. If you choose, however, to subclass the Message type, you might need to call one of these constructors to populate the header of the resultant Message object.
One of these constructors accepts one parameter of type MessageHeaders. This constructor performs a deep copy of the contents of the MessageHeaders parameter and stores that copy internally in the new MessageHeaders instance.
Another constructor accepts a parameter of type MessageVersion and, as you might expect, sets the SOAP version and WS-Addressing versions of the resultant MessageHeaders instance accordingly. The last constructor accepts a parameter of type MessageVersion and an Int32. This constructor assigns the SOAP and WS-Addressing versions, as well as the initial number of elements in the internal list of header blocks. Keep in mind that the actual number of elements in the list can grow beyond the value of the Int32 parameter. If we know the number of header blocks we are going to add to a MessageHeaders object, using this overload has a slight performance benefit since the internal storage mechanism can be sized properly early in the life cycle of the object.
Once a MessageHeaders object is instantiated, we will often need to add one or more MessageHeader objects to it. The MessageHeaders type defines an Add method that accepts a MessageHeader object as a parameter. The Add method inserts the MessageHeader parameter to the end of the list of existing header blocks.
If we need to insert a MessageHeader object in a specifc order, we can use the Insert method. This method accepts a parameter of type Int32 and another of type MessageHeader. The Int32 parameter represents the index we want to insert the MessageHeader into, and the MessageHeader parameter is, of course, the object whose value we want to store. It is interesting to note that a MessageHeaders object stores its header blocks as an array-hence the indexing semantics. If we pass an index value that is greater than the size of the array, the method throws an ArgumentOutOfRangeException.
When an application receives, decodes, and deserializes a stream into a Message object, we frequently need to get the values of one or more header blocks. Since the MessageHeader type offers no way to do this, we must turn to the MessageHeaders type.
One way we can find a particular MessageHeader in a MessageHeaders object is to find it by index. To find the index of a particular header block, we can call one of the two FindHeader methods. Both of these methods accept String parameters that represent the name and namespace of the header block. One of these methods accepts a String that represents the actors that can interact with that header block. The return type of both of these methods is an Int32. If a matching header block is not found, the FindHeader method behaves badly-it returns a –1. If duplicate header blocks are present, the method returns the index of the first one found.
Note | In my view, this is a bad design since it runs counter to the best practices outlined in all of the Microsoft documentation and internal standards regarding framework design. It would have been better to name these methods TryFindHeader or throw an exception of some sort if a matching header block is not found. Regardless of my opinion, we must now check for the value -1 when calling either of the FindHeader methods. |
After we have found the index (as long as it isn’t –1) of the header block, we must then retrieve the value of the header block. To do this, we call one of the GetHeader<T> methods. The overloads of this method accept a variety of parameters, including the index of the header block and a custom serializer. Three of these overloads accept String parameters that map to the parameters of the FindHeader methods. Internally, these overloads call the appropriate FindHeader method and check for the return value of –1 accordingly. In contrast to the FindHeader method, if a matching header block is not found, the GetHeader<T> methods throw an exception.
The MessageHeaders type provides several mechanisms to copy one or all of the header blocks from one MessageHeaders object to another. To see where this is useful, consider what is required to generate a Message that is a reply to a received Message. If the received Message contains a PurchaseOrderInfo header block, we might need to include a copy of that header block in the reply Message. While it is possible to simply create a new header block with the same values, it would be simpler to copy the existing header block into the new Message.
The two CopyHeaderFrom instance methods provide the capability to copy the value of one header block into the MessageHeaders instance. Both methods accept an Int32 parameter that indicates the index of the source header block. Both CopyHeaderFrom methods add the header block to the end of the internal array of header blocks, and there is no way to specify the destination index. One of the CopyHeaderFrom methods accepts a Message object as a parameter, while the other one accepts a MessageHeaders object as a parameter. Internally, the former calls the latter by means of the Headers instance property in the Message type.
The two CopyHeadersFrom instance methods provide the ability to copy the entire contents of one MessageHeaders object into another. There is an overload that accepts a Message object as a parameter, and another that accepts a MessageHeaders object as a parameter. Source header blocks are added to the end of the destination header blocks. In other words, this operation is more of a concatenation to the existing header blocks, rather than a complete replacement. This can easily have some unintended consequences, as shown in the following code snippet:
// create a Message Message message = Message.CreateMessage( MessageVersion.Soap12WSAddressing10, "urn:SomeAction", "Hello WCF"); // add two new headers to the Message message.Headers.To = new Uri("http://wintellect.com/Original"); message.Headers.Add(MessageHeader.CreateHeader("test", "test", "test")); // create a new Message Message message2 = Message.CreateMessage( MessageVersion.Soap12WSAddressing10, "urn:SomeAction2", "Hello WCF2"); // add two new headers to the Message message2.Headers.To = new Uri("http://wintellect.com/Test"); message2.Headers.Add(MessageHeader.CreateHeader("test", "test", "test")); // copy the headers from the first Message into the second one message2.Headers.CopyHeadersFrom(message); // show the contents Console.WriteLine(message2.ToString());
When this code executes, the following output is generated:
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope"> <s:Header> <a:Action s:mustUnderstand="1">urn:SomeAction2</a:Action> <a:To s:mustUnderstand="1">http://wintellect.com/Test</a:To> <test xmlns="test">test</test> <a:Action s:mustUnderstand="1">urn:SomeAction</a:Action> <a:To s:mustUnderstand="1">http://wintellect.com/Original</a:To> <test xmlns="test">test</test> </s:Header> <s:Body> <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/"> Hello WCF2 </string> </s:Body> </s:Envelope>
Oops. Clearly there is a problem with this Message. In reality, the CopyHeaderFrom methods suffer from the same malady (duplicate header blocks). In other words, copying header blocks is a fairly tricky business, and the onus is on the developer to check for duplicate header blocks in the destination Message.
The MessageHeaders type defines several methods that serialize all or part of a MessageHeaders object. Like the Message and the MessageHeader types, the serialization methods on the MessageHeaders type start with the word Write. The simplest of these methods is the WriteHeader method. As implied from its name, this method serializes one header block. It accepts an Int32 and an XmlDictionaryWriter as parameters. The Int32 parameter represents the index of the header block to serialize, and the XmlDictionaryWriter is, as you might have guessed, the object that performs the actual serialization and encoding. The implementation of the WriteHeader method calls two other MessageHeaders serialization methods: the WriteStartHeader and the WriteHeaderContents methods. The WriteStartHeader method, as its name implies, serializes the start of the header block, while the WriteHeaderContents method serializes the contents of the header block.
There is no one-step mechanism to serialize the entire contents of a MessageHeaders object. The only way to serialize all of the header blocks is to iterate over the header blocks and serialize each one. In practice, we seldom have the need to serialize header blocks outside the context of serializing a Message. To this end, the Message type defines the WriteMessage methods that serialize the entire contents of the Message. The implementation of the WriteMessage method on the Message type, however, iterates over and serializes each header block one at a time.
In the section “The MessageHeader Type” earlier in this chapter, we examined some of the considerations for using a MessageHeader to represent a WS-Addressing endpoint reference. We will seldom, if ever, need to manually work with a MessageHeader that represents an endpoint reference, because the MessageHeaders type defines several properties that represent an endpoint reference. In other words, the MessageHeaders type defines several properties that will add, change, or remove WS-Addressing header blocks and is primarily used to assign these header blocks to an instance of a Message (via the Headers property of a Message).
More specifically, the MessageHeaders type defines the following endpoint reference–related properties: From, ReplyTo, FaultTo, and To. The From, ReplyTo, and FaultTo properties are of type EndpointAddress. As previously mentioned, the EndpointAddress type is the common language runtime abstraction of a WS-Addressing endpoint reference. We will examine the EndpointAddress type in more detail in the next section. Following the letter of the law as stated in WS-Addressing, the To property is of type Uri.
The MessageHeaders type also defines properties that relate to other parts of the WS-Addressing specification. For example, the Action, MessageId, and RelatesTo properties map to the similarly named WS-Addressing header blocks. The Action property is of type String and is fairly straightforward. In a nutshell, when this property is set, a WS-Addressing Action header block is serialized when the Message is serialized.
The MessageId and RelatesTo properties are of type UniqueId, and are also fairly straightforward. The UniqueId type is a GUID-like construct, but it can also take the shape of other types through the use of the constructor overloads. Consider the following code snippet:
UniqueId uniqueId = new UniqueId(); Console.WriteLine(uniqueId.ToString()); uniqueId = new UniqueId("myuniquevalue"); Console.WriteLine(uniqueId.ToString());
When this code executes, the following output is generated:
urn:uuid: myuniquevalue
Notice that the value of a UniqueId object can be either a GUID-like value or an arbitrary String value. This functionality is required since the the MessageId and RelatesTo WS-Addressing header blocks are of type xs:Uri. In other words, any value can be placed in these fields. Since WCF is WS-Addressing compliant, a System.Guid cannot be used to represent these properties.
The EndpointAddress type serves two functions: it is an easy-to-use type that stores destination address information, and it is a means to serialize a WS-Addressing endpoint reference into a Message. In other words, the EndpointAddress type is part of the commonly used API, but it also plays a critical role in Message serialization and deserialization.
An EndpointAddress object wraps a System.Uri object. As a result, all of the EndpointAddress constructors accept a System.Uri, in some form or fashion, as a parameter. More specifically, five of the six constructors accept a Uri as a parameter, and one accepts a String as a parameter. The constructor that accepts a String internally generates a Uri from that String and then calls one of the other constructors. This feature of the EndpointAddress simply makes the type more usable, as shown here:
EndpointAddress address1 = new EndpointAddress("http://wintellect.com/OrderStuff"); Console.WriteLine("Address1: {0}",address1.ToString()); EndpointAddress address2 = new EndpointAddress( new Uri("http://wintellect.com/OrderStuff")); Console.WriteLine("Address2: {0}", address2.ToString()); Console.WriteLine("address1 {0} address2", (address1 == address2) ? "equals" : "does not equal");
When this code executes, the following output is generated:
Address1: http://wintellect.com/OrderStuff Address2: http://wintellect.com/OrderStuff Address1 equals Address2
Notice that the String rendering of the Uri is returned from the ToString method, rather than the String representation of a serialized EndpointAddress. Also notice that both constructors create the equivalent EndpointAddress object. (The operator overload on the EndpointAddress type checks the internal state for equivalence.)
There are several other constructor overloads that accept parameters of type AddressHeader, AddressHeaderCollection, EndpointIdentity, and XmlDictionaryReader. The most notable of these parameters is the AddressHeader type, and that is where we will begin.
The AddressHeader type is the common language runtime abstraction of a WS-Addressing reference parameter, and it simplifies the work required to add a reference parameter to a Message before serialization, as well as read the value of a reference parameter after Message deserialization. When one first approaches the AddressHeader type, there is commonly some confusion surrounding the differences between it and the MessageHeader type. These types do not share a common hierarchy, but they still serialize to the header of a SOAP message. The main difference is in their purpose: the AddressHeader type models a reference parameter, and the MessageHeader type models more general purpose header blocks.
From an object model perspective, the AddressHeader type is similar to the Message and MessageHeader types in that it is an abstract type that defines several factory methods, Write methods, and Get methods. (MessageHeader does not define Get methods, however.) The purpose of these methods in the AddressHeader type is consistent with the purpose of these methods in the Message and MessageHeader types and does not warrant repetition. I will leave it to the reader to experiment with these methods, if you are compelled to do so.
An EndpointAddress is most useful when referenced from a Message object. This is typically done through the Headers property of the Message type. For example, we can instantiate an EndpointAddress and assign that EndpointAddress to the FaultTo address of a Message, as shown here:
String uriValue = "http://wintellect.com/someService"; AddressHeader header = AddressHeader.CreateAddressHeader("ref param"); EndpointAddress address = new EndpointAddress(new Uri(uriValue), new AddressHeader[1] { header }); // notice the use of the AddressHeader Message myMessage = Message.CreateMessage( MessageVersion.Soap12WSAddressing10, "urn:SomeAction", "Hello There"); myMessage.Headers.FaultTo = address; Console.WriteLine(myMessage.ToString());
When this code executes, the following output is generated:
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/ 05/soap-envelope"> <s:Header> <a:Action s:mustUnderstand="1">urn:SomeAction</a:Action> <a:FaultTo> <a:Address>http://wintellect.com/someService</a:Address> <a:ReferenceParameters> <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/"> ref param </string> </a:ReferenceParameters> </a:FaultTo> </s:Header> <s:Body> <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/"> Hello There </string> </s:Body> </s:Envelope> <string a:IsReferenceParameter="true" xmlns="http://schemas.microsoft.com/203/10/Serialization/"> ref param </string> </s:Header> <s:Body> <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/"> Hello There </string> </s:Body> </s:Envelope>
Notice that the AddressHeader is populated in the WS-Addressing FaultTo endpoint reference as a reference parameter.
Because the To message header in WS-Addressing is an xs:uri, it is reasonable to wonder how we can use the EndpointAddress type in this critically important header. As you saw previously, the To property of the MessageHeaders type accepts a System.Uri, so we cannot set the To property directly with an EndpointAddress. The EndpointAddress defines the ApplyTo instance method, and thereby solves our dilemma. The ApplyTo method accepts a parameter of type Message and adds the state of the EndpointAddress to the Message passed as a parameter, as shown here:
String uriValue = "http://wintellect.com/someService"; AddressHeader header = AddressHeader.CreateAddressHeader("ref param"); EndpointAddress address = new EndpointAddress(new Uri(uriValue), new AddressHeader[1] { header }); // notice the use of the AddressHeader Message myMessage = Message.CreateMessage( MessageVersion.Soap12WSAddressing10, "urn:SomeAction", "Hello There"); address.ApplyTo(myMessage); Console.WriteLine(myMessage);
When this code executes, the following output is generated:
<s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.3.org/2003/05/soap-envelope"> <s:Header> <a:Action s:mustUnderstand="1">urn:SomeAction</a:Action> <a:To s:mustUnderstand="1">http://wintellect.com/someService</a:To> <string a:IsReferenceParameter="true" xmlns="http://schemas.microsoft.com/203/10/Serialization/"> ref param </string> </s:Header> <s:Body> <string xmlns="http://schemas.microsoft.com/2003/10/Serialization/"> Hello There </string> </s:Body> </s:Envelope>
Notice that the EndpointAddress (including the AddressHeader) was assigned to the Message object and that the reference parameter attribute is flagged as per the WS-Addressing specification.