Introducing Message Contracts

When using data contracts, usually your concentration is on the data structure and the serialization aspects of those structures and not so much on the SOAP message, which carries the "payload" between the service and the consumer. In other words, the data contracts control the format of the SOAP message. However, if you are in a situation where you want an equal amount of control over both the structure and the content of the message because of operational reasons, then you need to use message contracts as opposed to data contracts. When using message contracts, you can use a data type as either the parameter to a service call or the return value from a call. And it is this data type that is precisely serialized to the SOAP message, defining the precise schema for the message. Another way to put it is a message contract is nothing but a mapping for a data type and the SOAP envelope that is created.

Applying the [MessageContract] attribute to a type defines its message contract. Those type members that need to be part of the SOAP header need to have the [MessageHeader] attribute applied, and those that will be part of the SOAP body need to have the [MessageBodyMember] attribute applied to them. You can apply both the [MessageHeader] attribute and the [MessageBodyMember] attribute to all members of a type irrespective of their accessibility levels.

Similar to the data contracts, you can use the Name and Namespace properties on the [MessageHeader] and [MessageBodyMember] attributes. If the namespace is not changed, the default is the same namespace of the service contract. Listing 11-9 shows the earlier example of QuickReturnStockQuote from Listing 11-2, which implements a message contract instead of a data contract. This allows you to precisely control the schema of the message when QuickReturnStockQuote is the data type.

Example. QuickReturnStockQuote Implementing a Message Contract
[MessageContract]
public class QuickReturnStockQuote
{
    [MessageHeader(Name="TickerSymbol")]
    internal string Symbol;

    [MessageHeader]
    internal string CompanyName;

    [MessageBodyMember]
    internal decimal LastTrade;

    [MessageBodyMember]
    internal decimal Change;

    [MessageBodyMember]
    internal decimal PreviousClose;

    [MessageBodyMember(Name = "AverageVolume")]
    internal decimal AvgVol;

    [MessageBodyMember(Name = "MarketCapital")]
    internal double MarketCap;

    [MessageBodyMember(Name = "PriceEarningRatio")]
    internal decimal PERatio;

[MessageBodyMember(Name = "EarningsPerShare")]
    internal decimal EPS;

    [MessageBodyMember(Name = "52WkHigh")]
    internal decimal FiftyTwoWeekHigh;

    [MessageBodyMember(Name = "52WkLow")]
    internal decimal FiftyTwoWeekLow;
}

Listing 11-10 shows the SOAP representation of QuickReturnStockQuote.

Example. SOAP Message Representation of QuickReturnStockQuote
<soap:Envelope>
    <soap:Header>
        <TickerSymbol>MSFT</TickerSymbol>
        <CompanyName>Microsoft</CompanyName>
    </soap:Header>
    <soap:Body>
        <LastTrade>29.24</LastTrade>
        <Change>0.02</Change>
        <PreviousClose>29.17</PreviousClose>
        <AverageVolume>59.31</AverageVolume>
        <MarketCapital>287.44</MarketCapital>
        <PriceEarningRatio>24.37</PriceEarningRatio>
        <EarningsPerShare>1.20</EarningsPerShare>
        <_52WkHigh>29.40</_52WkHigh>
        <_52WkLow>21.45</_52WkLow>
    </soap:Body>
</soap:Envelope>

Fine-Tuning SOAP

As we have stated, a message contract is all about fine-tuning the various aspects of a SOAP envelope, empowering you with the ability to integrate with other platforms and also those that have a special interoperability need. In this section, you will see what options you have to customize the SOAP envelope; we'll cover aspects such as SOAP wrappers, element order, SOAP actions, SOAP header attributes, and so on.

You can also control the wrapping of the SOAP body parts. The default behavior is to have the body parts in a wrapper element when serialized. More than one body part should not be wrapped because it is not compliant with WS-I Basic Profile (version 1.1). The only situation where you would do this is for interoperability in some specific scenarios to another system that expects this format. You can control the name and the namespace of the wrapper element by setting the WrapperName and WrapperNamespace properties in the [MessageContract] attribute.

The default order of elements is alphabetical; however, both the [MessageHeader] and [MessageBodyMember] attributes support the Order property, which can be used to set the specific ordering of elements the same as in a data contract. The only difference when compared to the data contract is the inheritance scenario. Unlike the data contract, in a message contract the base type's members are not sorted before the child type's members.

If you need to implement a SOAP action, then you need to define that with the service operation of the service contract through the [OperationContract] attribute. To specify the SOAP action, you need to set the Action and ReplyAction properties on the [OperationContract] attribute when defining the service operation. The SOAP specification allows the three attributes listed in Table 11-2 in the header. By default, these headers are not emitted, but they can be set via the Actor, MustUnderstand, and Relay properties on the [MessageHeader] attribute, respectively. Note, if you have the property MustUnderstand set to true and you have a new version of the message contract, then you will get an exception at runtime because there is an extra header in the SOAP message that is "not understood."

Table Valid SOAP Header Attributes
valueDescription
Actor (version 1.1)/Role(version 1.2)Header's target URI
MustUnderstandSpecifies whether the node processing the header must understand it or not
RelaySpecifies whether the header can be relayed downstream to other nodes

At times a service might be required to support legacy XML. This is especially true in integration and interop situations where the platforms might differ between the consumer and the service. If required, you can enable the legacy XML encoding by setting the Use property on the [XmlSerializerFormat] attribute to Encoded, as shown in Listing 11-11. However, this is not recommended for two reasons. First, arrays are not supported, and second, it's because object references are preserved within the message body.

Example. QuickReturnStockQuote Using Legacy SOAP Encoding
[XmlSerializerFormat(Use=OperationFormatUse.Encoded)]
public class QuickReturnStockQuote
{
    [DataMember(Name = "TickerSymbol")]
    public string Symbol;

    [DataMember]
    public string CompanyName;

    [DataMember]
    public decimal LastTrade;

    //Abbreviated for Clarity
}

The only time a message type can inherit from another type is when the base type also has a message contract. Also when inheriting, the message headers are a collection of all the headers in the inheritance hierarchy. Similarly, all the body parts are also consolidated in the inheritance hierarchy and are ordered first by the Order property specified in the [MessageBodyMember] attribute (if any) and then alphabetically. If the same name for either the header or the body part is repeated in the inheritance hierarchy, then the member that is lowest in the hierarchy is used to store the information.

NOTE

If required, the WCF runtime allows you to use your own serializer by inheriting the XmlObjectSerializer class and overriding the WriteStartObject, WriteObjectContent, and WriteEndObject members.

You also need to consider the legacy XML support. If there is a requirement by your service to produce WSDL for interop scenarios, you need to treat this with care. WSDL and message contract support is tricky because WSDL supports only a subset of the message contract features. As a result, when generating WSDL, all the features from a message contract will not get reflected because of the lack of this support. You should consider these points when working with WSDL:

  • WSDL does not have the notion of an array of headers and will show only one header as opposed to the array.

  • Similar to the previous point, protection-level information is not fully supported and may be missing.

  • The class name of the message contract type will be the message type generated in the WSDL.

  • If many operations in a service contract use the same message contract across those operations, then the WSDL that is generated for that service contract will contain multiple message types even though at the end of the day they are the same type. These multiple messages are made unique in the WSDL by appending a numeral at the end such as 2, 3, and so on. As a result, the message types created when importing such a WSDL are identical except for their names.

Security

You have three options to make a message secure when using message contracts. Depending on which of the three options you choose, different parts of a SOAP message are digitally signed and encrypted. The options you have are to secure the entire SOAP message, to secure only the header of the SOAP message, or to secure only the body of the SOAP message (that is, the payload). To enable the option you choose, set the ProtectionLevel property on either the [MessageHeader] attribute or the [MessageBodyMember] attribute. Although for each header the protection level is determined individually, the body's security level is determined collectively with the highest level being applied across all body parts. For these security options to work, the bindings and behaviors need to be set up correctly (for example, attempting to sign without providing the correct credentials); otherwise, you will get an exception. Table 11-3 summarizes the possible values of this property.

Table ProtectionLevel Property Values
ValueDescription
NoneNo encryption or digital signature (this is also the default option
SignDigital signature only
EncyiptAndSignBoth digitally signs and encrypts

Performance

Since every message header and body part is serialized independent of each other, the same namespace will be repeatedly declared for each of the same. It is recommended you consolidate these multiple headers and body parts into a single header or body part to reduce the size of the message on the wire and improve performance. For example, you can rewrite the original Listing 11-8 that showed QuickReturnStockQuote implemented as a message contract as shown in Listing 11-12.

Example. QuickReturnStockQuote Implemented for Optimal Performance
[MessageContract]
public class QuickReturnStockQuote
{
    [MessageHeader(Name="TickerSymbol")]
    internal string Symbol;

    [MessageHeader]
    internal string CompanyName;

    [MessageBodyMember]
    internal StockDetails StockInformation;
}

[DataContract]
public class StockDetails {
    [DataMember]
    internal decimal LastTrade;

    [DataMember]
    internal decimal Change;

    [DataMember]
    internal decimal PreviousClose;

[DataMember(Name = "AverageVolume")]
    internal decimal AvgVol;

    [DataMember(Name = "MarketCapital")]
    internal double MarketCap;

    [DataMember(Name = "PriceEarningRatio")]
    internal decimal PERatio;

    [DataMember(Name = "EarningsPerShare")]
    internal decimal EPS;

    [DataMember(Name = "52WkHigh")]
    internal decimal FiftyTwoWeekHigh;

    [DataMember(Name = "52WkLow")]
    internal decimal FiftyTwoWeekLow;
}

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset