Chapter 2. Service Contracts and Data Contracts

WHAT'S IN THIS CHAPTER?

  • Explaining contracts

  • Implementing service contracts

  • Implementing data contracts

  • Using different serializers

  • Implementing message contracts

Contracts in general are very important in our daily lives. Contracts ensure that each party is aware of what his or her efforts will return. If you put your signature at the end of 17 pages of a business document or make a verbal agreement, expectations are given and agreed on. A contract should be well-defined and understandable without leaving any leeway for interpretation.

One thing is certain: in a service-oriented environment where different parties (software components) communicate with each other and use different technologies and platforms, it is indispensable to have a clear definition of the how and what.

WCF (Windows Communication Foundation) uses the concept of contracts to define the service and its operation as a whole, to explain the data which are transferred by wire, and if needed, to directly define the SOAP messages exchanged between the client and the service. WCF uses WSDL and XSD to deliver service metadata.

WCF differentiates between three types of contracts: service, data, and message.

Each of these contracts defines a certain behavior. The one needed depends on your architecture. The most frequently used contract type is the service contract — it can be used in nearly every scenario. The service contract also stands for the C in the ABC of an endpoint. As you may know, a service endpoint is an addressable unit which is typically used by the client to send and receive messages.

Contracts in the context of SOA provide the necessary metadata to communicate with the service — they describe things such as data types, operations, message exchange patterns, and the transport protocol that are used. Contracts are typically delivered in a standardized platform and language-neutral XML format that allow different parties to consume and interpret the definition. In WCF, the service metadata is typically described by a WSDL document (Web Services Description Language). More information is available at http://www.w3.org/TR/wsdl. This standard allows software vendors such as Microsoft, Sun, and IBM to build tools to help developers produce and consume these contracts.

SERVICE CONTRACTS

The service contract defines the functionality which is exposed by your service to the outside world. The functionality is expressed in the form of service operations. In a SOA world, you typically exchange a set of SOAP messages, and the schemas of these messages are defined in the service contract. The service contract further defines the message exchange pattern (Request/Reply, One-Way, Duplex) for each service operation.

DATA CONTRACTS

You usually pass one or more arguments to your service operation and expect a return value. The structure of a message argument is explained in the data contract as an XSD schema which is part of the WSDL document. The Data Contract defines the structure and content of the information that is exchanged between the client and the service.

If you use your own objects and complex types as method arguments or return values, you have to inform the runtime how to serialize these types into an XML stream by using the DataContract attribute.

Depending on the granularity of your method arguments, you might use only simple .NET types as method arguments. In this case you do not need to worry about serialization and deserialization because the messaging infrastructure already knows how to deal with those simple .NET types.

MESSAGE CONTRACTS

A message contract is an advanced feature which can be used to gain greater control of the SOAP header and/or the SOAP body. The message contract is used to directly interact with the SOAP message (header and body), not just the method body — as is the case if you use a data contract instead of a message contract.

CONTRACTS AND CODE

As a .NET software developer on the service side, it is not your daily job to produce metadata documents in the form of WSDL — you would normally design interfaces and classes, write methods, implement business logic, and then write unit tests to check your logic. To help software developers produce these metadata artifacts from source code without fundamentally changing the programming, WCF offers different tools, attributes, and classes which are explained in more detail on the following pages.

If you use WCF to implement your SOA architecture, you can choose from different approaches — depending on your requirements — to automatically generate those metadata documents from scratch, based on your code. You can use .NET attributes, configuration settings, and directly leverage the API — provided by WCF — to get absolute control of the metadata generation.

But how can you consume those metadata from a client and interact with the service? To complete the story, assume that you have already generated the service specification in the form of a WSDL document. Then assume that the service is up and running. If you want to communicate with the service, you have to create an XML message in the right format, send the messages to the service endpoint, wait for a response message, and finally extract your values from the XML document. Thanks to the standard of WSDL and tools such as svcutil.exe and VS 2010, it is very easy to create source code (a proxy) which mimics the behavior of the service and handles all the communication and serialization details for you.

As mentioned earlier, there are different approaches to instructing the runtime. Knowing which parts of your code should be reflected as metadata in WSDL, without knowing the exact specification of WSDL, is what you need to figure out.

It is helpful to know some basic elements of WSDL to get a good understanding of what is going on under the hood and to interpret the generated metadata document. To get a basic idea of the correlation between elements in the WSDL document and your code, start with a simple example to see which code fragment leads to which element in the WSDL document.

CAR RENTAL SERVICE — IMPLEMENTATION EXAMPLE

So far contracts in general have been explained — automatic metadata generation and the use of proxies. Now it is time to see all these concepts in action.

To give you a short and compact overview of how to create a service contract, a simplified implementation of a fictive car rental service is shown, explaining in further detail about the basis of the code listings. If you choose the contract-first approach, you typically start by defining the WSDL document and not by writing code.

When using the code-first approach, you start with a .NET interface definition and decorate your service contract with the [ServiceContract] attribute and your methods with the [OperationContract] attribute from the System.ServiceModel namespace.

Step 1: Service Contract Definition

The service contract definition as shown in Listing 2-1 should live in its own assembly because there are several advantages if you use a separate Class Library for your interface and your service contract — such as versioning, decoupling the abstract definition from the concrete implementation, and deployment.

Example 2.1. Service Contract Definition

using System;
using System.ServiceModel;

namespace Wrox.CarRentalService.Contracts
{
  [ServiceContract()]
  public interface ICarRentalService
  {
   [OperationContract]
   double CalculatePrice(DateTime pickupDate, DateTime returnDate,
   string pickupLocation, string vehiclePreference);
  }
}

As you can see in Listing 2-1, the example service interface is quite simple and contains just one method called CalculatePrice with four arguments and a return type of double.

To define a service contract, just apply the [ServiceContract] attribute to the interface definition and the [OperationContract] attribute to each method you want included as a service operation in the WSDL document. If the interface contains a method without the [OperationContract] attribute, the method will not be included in the generated service metadata. This is also called an opt-in model.

It is possible to annotate a class — the concrete service implementation — with the [ServiceContract] attribute and its methods with the [OperationContract] attribute. However, there are several reasons why this is not recommended. One reason is that you will mix the "outside world" (the service contract) with the "inside world" (your implementation code), and this breaks the rule about having explicit boundaries. You should always decouple the abstract service definition from the concrete implementation, if you want to produce production code.

If you apply the attribute at the interface level, you can additionally create different implementations of the same service contract and expose those different implementations at various service endpoints.

After compiling the source code, obtain an Assembly called Wrox.CarRentalService.Contracts.dll which contains the interface/service contract definition.

Step 2: Extract the Service Metadata

To give you a better understanding of how each part of the interface definition correlates with the WSDL document, the WSDL document is generated first.

If the service exposes a Metadata Exchange Endpoint (MEX), you can directly browse to this endpoint and see the generated metadata.

Because you do not have a MEX, you will use svcutil.exe to generate the WSDL file. The ServiceModel Metadata Utility Tool (svcutil.exe) can be found at the Windows SDK installation location and provides you with a number of functionalities:

  • Code generation from running services or static metadata documents

  • Metadata export from compiled code

  • Validation of compiled service code

  • Download of metadata documents from running services

  • Generation of serialization code

Use the command svcutil.exe Wrox.CarRentalService.Contracts.dll at the Visual Studio command prompt to get the three files shown in Table 2-1.

Table 2.1. The WSDL and XSD Files

FILENAME

DESCRIPTION

tempuri.org.wsdl (see Listing 2-2)

The WSDL document with a reference to the XSD files, message definitions, port types, and service operations.

tempuri.org.xsd (see Listing 2-3)

The XSD schema for the method attributes.

schemas.microsoft.com.2003.10.Serialization.xsd (see Listing 2-4)

The standard XSD schema for simple .NET types.

Note

Visit the W3C site at http://www.w3.org/XML/Schema for more information about schemas.

Example 2.2. WSDL

<?xml version="1.0" encoding="utf-8"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
 xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/
oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
xmlns:tns="http://tempuri.org/"
xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing"
xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy"
xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy"
xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl"
xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract"
xmlns:wsa10="http://www.w3.org/2005/08/addressing"
xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex"
xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata"
targetNamespace="http://tempuri.org/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
  <wsdl:types>
    <xsd:schema targetNamespace="http://tempuri.org/Imports">
      <xsd:import namespace="http://tempuri.org/" />
      <xsd:import namespace="http://schemas.microsoft.com/2003/10/
Serialization/" />
    </xsd:schema>
  </wsdl:types>
  <wsdl:message name="ICarRentalService_CalculatePrice_InputMessage">
    <wsdl:part name="parameters" element="tns:CalculatePrice" />
  </wsdl:message>
  <wsdl:message name="ICarRentalService_CalculatePrice_OutputMessage">
    <wsdl:part name="parameters" element="tns:CalculatePriceResponse" />
  </wsdl:message>
  <wsdl:portType name="ICarRentalService">
    <wsdl:operation name="CalculatePrice">
      <wsdl:input wsaw:Action="http://tempuri.org/ICarRentalService/CalculatePrice"
message="tns:ICarRentalService_CalculatePrice_InputMessage" />
      <wsdl:output wsaw:Action="http://tempuri.org/ICarRentalService/
CalculatePriceResponse" message=
"tns:ICarRentalService_CalculatePrice_OutputMessage" />
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="DefaultBinding_ICarRentalService"
type="tns:ICarRentalService">
    <soap:binding transport="http://schemas.xmlsoap.org/soap/http" />
    <wsdl:operation name="CalculatePrice">
      <soap:operation soapAction="http://tempuri.org/ICarRentalService/
CalculatePrice" style="document" />
      <wsdl:input>
        <soap:body use="literal" />
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal" />
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
</wsdl:definitions>

Example 2.3. XSD File

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:tns="http://tempuri.org/" elementFormDefault="qualified"
targetNamespace="http://tempuri.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="CalculatePrice">
    <xs:complexType>
      <xs:sequence>
        <xs:element minOccurs="0" name="pickupDate" type="xs:dateTime" />
        <xs:element minOccurs="0" name="returnDate" type="xs:dateTime" />
        <xs:element minOccurs="0" name="pickupLocation" nillable="true"
type="xs:string" />
        <xs:element minOccurs="0" name="vehiclePreference"
nillable="true" type="xs:string" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="CalculatePriceResponse">
    <xs:complexType>
      <xs:sequence>
        <xs:element minOccurs="0" name="CalculatePriceResult" type="xs:double" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

Example 2.4. schemas.microsoft.com.2003.10.Serialization

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:tns=http://schemas.microsoft.com/2003/10/Serialization/
 attributeFormDefault="qualified" elementFormDefault="qualified"
targetNamespace="http://schemas.microsoft.com/2003/10/Serialization/"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="anyType" nillable="true" type="xs:anyType" />
  <xs:element name="anyURI" nillable="true" type="xs:anyURI" />
  <xs:element name="base64Binary" nillable="true"
type="xs:base64Binary" />
  <xs:element name="boolean" nillable="true" type="xs:boolean" />
  <xs:element name="byte" nillable="true" type="xs:byte" />
  <xs:element name="dateTime" nillable="true" type="xs:dateTime" />
  <xs:element name="decimal" nillable="true" type="xs:decimal" />
  <xs:element name="double" nillable="true" type="xs:double" />
  <xs:element name="float" nillable="true" type="xs:float" />
  <xs:element name="int" nillable="true" type="xs:int" />
  <xs:element name="long" nillable="true" type="xs:long" />
  <xs:element name="QName" nillable="true" type="xs:QName" />
  <xs:element name="short" nillable="true" type="xs:short" />
  <xs:element name="string" nillable="true" type="xs:string" />
  <xs:element name="unsignedByte" nillable="true"
type="xs:unsignedByte" />
  <xs:element name="unsignedInt" nillable="true"
type="xs:unsignedInt" />
  <xs:element name="unsignedLong" nillable="true"
type="xs:unsignedLong" />
  <xs:element name="unsignedShort" nillable="true"
type="xs:unsignedShort" />
  <xs:element name="char" nillable="true"
type="tns:char" />
  <xs:simpleType name="char">
    <xs:restriction base="xs:int" />
  </xs:simpleType>
  <xs:element name="duration" nillable="true"
type="tns:duration" />
  <xs:simpleType name="duration">
    <xs:restriction base="xs:duration">
      <xs:pattern value=
"-?P(d*D)?(T(d*H)?(d*M)?(d*(.d*)?S)?)?" />
      <xs:minInclusive value="-P10675199DT2H48M5.4775808S" />
      <xs:maxInclusive value="P10675199DT2H48M5.4775807S" />
    </xs:restriction>
  </xs:simpleType>
  <xs:element name="guid" nillable="true" type="tns:guid" />
  <xs:simpleType name="guid">
    <xs:restriction base="xs:string">
      <xs:pattern value=
"[da-fA-F]{8}-[da-fA-F]{4}-[da-fA-F]{4}-
[da-fA-F]{4}-[da-fA-F]{12}" />
    </xs:restriction>
  </xs:simpleType>
  <xs:attribute name="FactoryType" type="xs:QName" />
  <xs:attribute name="Id" type="xs:ID" />
  <xs:attribute name="Ref" type="xs:IDREF" />
</xs:schema>

As you can see in Listing 2-2 (WSDL) and Listing 2-3 (XSD), different parts of the XSD and WSDL come directly from your source code. To override the default naming behavior and gain further control, you can use the members of the [ServiceContract] and the [OperationContract] attributes. Table 2-2 describes the mapping between your code and the WSDL document.

Table 2.2. Mapping between Source Code and WSDL Elements

WSDL ELEMENT

SOURCE CODE

Message name: ICarRentalService_CalculatePrice

Interface name + method name + input/output

portType name: ICarRentalService

Interface name

Operation name: CalculatePrice

Method name

As you have seen so far, you can generate metadata from code by defining an interface, putting some methods in it, and decorating the interface name with the [ServiceContract] attribute and your methods with the [OperationContract] attribute. The WCF infrastructure uses reflection to build the WSDL document.

Step 3: Service Implementation

To complete the service side, you need one or more concrete implementations of the service interface. As a recommended practice, the concrete service implementation should be placed in a separate Class Library. As in Listing 2-5, there is no need to use WCF-specific elements in the service implementation code.

You may see the use of the [ServiceBehavior] and [OperationBehavior] attributes in the implementation code. Behaviors in general are internal implementation details and therefore are not included in the WSDL file.

Example 2.5. Service Implementation

using System;
using Wrox.CarRentalService.Contracts;

namespace Wrox.CarRentalService.Implementations.Europe
{
    public class CarRentalService: ICarRentalService
    {
        public double CalculatePrice
                      (
                      DateTime pickupDate,
                      DateTime returnDate,
                      string pickupLocation,
                      string vehiclePreference
                      )
        {
            //call to some internal business logic
            Random r = new Random(DateTime.Now.Millisecond);
            return r.NextDouble()*500;
        }
    }
}

Step 4: The Client

The metadata can be used to generate the necessary client code. For this purpose the client would use svcutil.exe or Add Service Reference from within Visual Studio to obtain a proxy which mimics the functionality of the service. You will find more details about this topic in Chapter 5. Listing 2-6 demonstrates the use of the generated proxy within your client app; Listing 2-7 displays the request SOAP message; and Listing 2-8 displays the response message.

Example 2.6. Client Code

using System;
using Wrox.CarRentalService.ConsoleClient.CarRentalProxy;

namespace Wrox.CarRentalService.ConsoleClient
{
    class Program
    {
        static void Main(string[] args)
        {
            using (CarRentalServiceClient
                   carRentalClient = new CarRentalServiceClient())
            {
                double price = carRentalClient.CalculatePrice
                       (
                        DateTime.Now, DateTime.Now.AddDays(5),
                        "Graz", "Pickup"
                       );
                Console.WriteLine("Price {0}",price );
            }
        }
    }
}

Example 2.7. SOAP Messages

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
 <s:Body>
  <CalculatePrice xmlns="http://tempuri.org/">
   <pickupDate>2010-01-09T19:15:25.2392813+01:00</pickupDate>
   <returnDate>2010-01-14T19:15:25.2402578+01:00</returnDate>
   <pickupLocation>Graz</pickupLocation>
   <vehiclePreference>Pickup</vehiclePreference>
  </CalculatePrice>
 </s:Body>
</s:Envelope>

Example 2.8. SOAP Response Message

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
 <s:Body>
  <CalculatePriceResponse xmlns="http://tempuri.org/">
   <CalculatePriceResult>357.7265724808567</CalculatePriceResult>
  </CalculatePriceResponse>
 </s:Body>
</s:Envelope>

[ServiceContract] and [OperationContract]

The automatically generated WSDL document sometimes needs further enhancements — you should at least provide a meaningful namespace for the service to prevent name clashes with other services. Sometimes there are some internal coding styles and naming patterns for interfaces, classes, and methods which are not allowed or recommended in a service description, or you just don't want to expose your internal names to the outside world.

To further enhance and gain additional control of the generated WSDL document, Table 2-3 (from MSDN) describes the members of the [ServiceContract] and the [OperationContract] in detail.

Table 2.3. [ServiceContract] Members

SETTING

DESCRIPTION

CallbackContract

Gets or sets the type of callback contract when the contract is a duplex.

ConfigurationName

Gets or sets the name used to locate the service in an application configuration file.

HasProtectionLevel

Gets a value that indicates whether the member has a protection level assigned.

Name

Gets or sets the name for the <portType> element in WSDL.

Namespace

Gets or sets the namespace of the <portType> element in WSDL.

ProtectionLevel

Specifies whether the binding for the contract must support the value of the ProtectionLevel property.

SessionMode

Gets or sets whether sessions are allowed, not allowed, or required.

As it applies to the [ServiceContract] attribute, the same goes for the [OperationContract] attribute — sometimes you need more control over the generation of the WSDL document. See Table 2-4.

Table 2.4. [OperationContract] Members

SETTING

DESCRIPTION

Action

Specifies the action that uniquely identifies this operation. WCF dispatches request messages to methods based on their action.

AsyncPattern

Indicates that the operation is implemented or can be called asynchronously using a Begin/End method pair.

HasProtectionLevel

Indicates whether the ProtectionLevel property has been explicitly set.

IsOneWay

Indicates that the operation only consists of a single input message. The operation has no associated output message.

IsInitiating

Specifies whether this operation can be the initial operation in a session.

IsTerminating

Specifies whether WCF attempts to terminate the current session after the operation completes.

ProtectionLevel

Specifies the message-level security that an operation requires at runtime.

Listing 2-9 demonstrates the use of some common attributes. Some of the other attributes are described in Chapter 3.

Example 2.9. [ServiceContract] and [OperationContract] Attributes

using System;
using System.ServiceModel;

namespace Wrox.CarRentalService.Contracts
{
    [ServiceContract(Namespace="http://wrox/CarRentalService/2009/10",
                     Name="RentalService")]
    public interface ICarRentalService
    {
        [OperationContract(Name = "GetPrice")]
        double CalculatePrice(DateTime pickupDate, DateTime returnDate,
        string pickupLocation, string vehiclePreference);

        [OperationContract( Name = "GetPriceOverloaded")]
        double CalculatePrice(string pickupLocation,
        string vehiclePreference);

        [OperationContract(IsOneWay=true,
        ProtectionLevel=System.Net.Security.ProtectionLevel.None )]
        void UpdatePrice(string vehicleId, double newPrice);
    }
}

The result of these attributes is reflected in the WSDL fragment shown in Listing 2-10.

Example 2.10. WSDL Document

<?xml version="1.0" encoding="utf-8"?>
<wsdl:definitions . . ..
xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace=
"http://wrox/CarRentalService/2009/10"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">

  <wsdl:message name="RentalService_GetPrice_InputMessage">
    <wsdl:part name="parameters" element="tns:GetPrice" />
  </wsdl:message>
  <wsdl:message name="RentalService_GetPrice_OutputMessage">
    <wsdl:part name="parameters" element="tns:GetPriceResponse" />
  </wsdl:message>
  <wsdl:message name="RentalService_GetPriceOverloaded_InputMessage">
    <wsdl:part name="parameters" element="tns:GetPriceOverloaded" />
  </wsdl:message>
  <wsdl:message name="RentalService_GetPriceOverloaded_OutputMessage">
    <wsdl:part name="parameters" element="tns:GetPriceOverloadedResponse" />
  </wsdl:message>
  <wsdl:message name="RentalService_UpdatePrice_InputMessage">
    <wsdl:part name="parameters" element="tns:UpdatePrice" />
  </wsdl:message>
  <wsdl:portType name="RentalService">
    <wsdl:operation name="GetPrice">
      <wsdl:input wsaw:Action=
"http://wrox/CarRentalService/2009/10/RentalService/GetPrice"
 message="tns:RentalService_GetPrice_InputMessage" />
      <wsdl:output wsaw:Action=
"http://wrox/CarRentalService/2009/10/RentalService/GetPriceResponse"
 message="tns:RentalService_GetPrice_OutputMessage" />
    </wsdl:operation>
    <wsdl:operation name="GetPriceOverloaded">
      <wsdl:input wsaw:Action=
"http://wrox/CarRentalService/2009/10/RentalService/GetPriceOverloaded"
 message="tns:RentalService_GetPriceOverloaded_InputMessage" />
      <wsdl:output wsaw:Action=
"http://wrox/CarRentalService/2009/10/RentalService
/GetPriceOverloadedResponse"
message="tns:RentalService_GetPriceOverloaded_OutputMessage" />
    </wsdl:operation>
    <wsdl:operation name="UpdatePrice">
      <wsdl:input wsaw:Action=
"http://wrox/CarRentalService/2009/10/RentalService/UpdatePrice"
 message="tns:RentalService_UpdatePrice_InputMessage" />
    </wsdl:operation>
  </wsdl:portType>
</wsdl:definitions>

DATA CONTRACTS

Previous examples have demonstrated the use of the attributes [OperationContract] and [ServiceContract]. The [OperationContract] attribute essentially determines which operations are provided by your service.

The data which is transferred depends on the individual transfer parameters and the return data type. The specific value of a parameter is typically derived from a .NET object in memory.

This object is converted into a corresponding form and is embedded in the SOAP message. On the other hand, the parameters are extracted from the SOAP message and provided to you as a .NET object. This conversion is performed by special serializer classes.

WCF supports different types of serialization/deserialization, whereby some serializers have been specially developed for WCF, and some have existed in the framework since .NET 1.0/.NET 2.0 and can also be used with WCF.

WCF uses the DataContractSerializer class from the namespace System.Runtime.Serialization as standard to serialize or deserialize its objects. This serializer has been specially developed for WCF and is particularly fast, effective, and powerful, which makes it the preferred choice. However, should your requirements exceed the capability of the DataContractSerializer, you can revert to classical XML serialization.

The DataContractSerializer supports various data types, including the following:

  • Primitive data types

  • Data types with the [DataContract] attribute

  • Classes which are marked as serializable

  • Classes which implement the IXmlSerializable interface from the namespace System.Xml.Serialization

  • Enumerations, collections, and generic collections

In the previous examples, there was no need for additional serialization measures because primitive data types are already serializable. However, if you have to work with complex data types as method parameters or return values, you have to use the appropriate attributes ([DataContract], [DataMember]) to determine how this data is serialized.

These two attributes from the namespace System.Runtime.Serialization are a design-time construct which tells the DataContractSerializer how to serialize a special type. Because the structure of the data being exchanged also belongs to the contract, of course, the application of the [DataContract] and [DataMember] attributes affects the prepared WSDL document directly. The definition of the data is incorporated into the contract (WSDL) in a format which is independent of platform and programming languages, namely XSD. Therefore, the client and the server cannot agree on the exchange of .NET data types, but are compatible with regard to the neutral XML schema definition format.

To this effect, look at the simple example in Listing 2-11 which does not include the use of the [DataContract] and [DataMember] attributes and produces a WSDL document shown in Listing 2-12.

Example 2.11. Service Operation with Simple Types

using System;
using System.ServiceModel;

namespace Wrox.CarRentalService.Contracts
{
    [ServiceContract(
             Namespace = "http://wrox/CarRentalService/2009/10",
             Name = "RentalService")]

    public interface ICarRentalService
    {
        [OperationContract]
        double CheckAvgPricePerDay(string carType);
    }
}

Example 2.12. WSDL Document with Simple Types

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:tns="http://wrox/CarRentalService/2009/10" elementFormDefault=
"qualified"
targetNamespace="http://wrox/CarRentalService/2009/10"
 xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="CheckAvgPricePerDay">
    <xs:complexType>
      <xs:sequence>
        <xs:element minOccurs="0" name="carType"
nillable="true" type="xs:string" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="CheckAvgPricePerDayResponse">
    <xs:complexType>
      <xs:sequence>
        <xs:element minOccurs="0"
name="CheckAvgPricePerDayResult"
type="xs:double" />
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

In the example defined in Listing 2-11, neither the [Serializable] attribute nor the [DataContract] attribute have been used with their corresponding [DataMember] attribute. This is possible because the DataContractSerializer class can work with simple .NET data types without the need for any precautionary measures.

However, in most cases you will not use simple data types as the content of your methods in a message-oriented architecture; instead, consideration should be given to the structure of the data being exchanged and make this known in the form of a contract.

In this case you can use the [DataContract] and [DataMember] attributes to tell the DataContractSerializer how and in which form its objects are to be converted.

If you use complex data types in your operations without informing the DataContractSerializer how they are to be handled, all publicly visible members are automatically serialized. This enables you to serialize POCOs (plain old CLR objects) without additional changes in your code. You can exclude individual members with the [IgnoreDataMember] attribute. Listing 2-13 shows a service contract with a method called CalculatePrice, an input parameter of type PriceCalculationRequest. The return type is defined as PriceCalculationResponse.

Example 2.13. Service Contract with Complex Types

namespace Wrox.CarRentalService.Contracts
{
    [ServiceContract(
               Namespace = "http://wrox/CarRentalService/2009/10",
               Name = "RentalService")]
    public interface ICarRentalService
    {
        [OperationContract]
        PriceCalculationResponse CalculatePrice
        (
            PriceCalculationRequest  request);
        }
    }
}

Listings 2-14 and 2-15 demonstrate the use of POCOs, the [IgnoreDataMember] attribute, and the resulting WSDL document.

Example 2.14. POCOs and [IgnoreDataMember]

using System;
using System.Runtime.Serialization;
using System.Xml.Serialization;

namespace Wrox.CarRentalService.Contracts
{
    public class PriceCalculationRequest
    {
        public DateTime PickupDate { get; set; }
        public DateTime ReturnDate { get; set; }
        public string PickupLocation { get; set; }
        public string ReturnLocation { get; set; }
        private string VehicleType { get; set; }
        [IgnoreDataMember]
        public string Color { get; set; }
    }
}

Example 2.15. WSDL Document

<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:tns="http://schemas.datacontract.org/2004/07/
Wrox.CarRentalService.Contracts"
elementFormDefault="qualified"
targetNamespace="http://schemas.datacontract.org/2004/07/
Wrox.CarRentalService.Contracts"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:complexType name="PriceCalculationRequest">
    <xs:sequence>
      <xs:element minOccurs="0" name="PickupDate" type="xs:dateTime" />
      <xs:element minOccurs="0" name="PickupLocation"
nillable="true" type="xs:string" />
      <xs:element minOccurs="0" name="ReturnDate" type="xs:dateTime" />
      <xs:element minOccurs="0" name="ReturnLocation"
nillable="true" type="xs:string" />
    </xs:sequence>
  </xs:complexType>
  <xs:element name="PriceCalculationRequest"
nillable="true" type="tns:PriceCalculationRequest" />
  <xs:complexType name="PriceCalculationResponse">
    <xs:sequence>
      <xs:element minOccurs="0" name="Flag" type="xs:int" />
      <xs:element minOccurs="0" name="Price" nillable="true" type="xs:string" />
    </xs:sequence>
  </xs:complexType>
  <xs:element name="PriceCalculationResponse"
nillable="true" type="tns:PriceCalculationResponse" />
</xs:schema>

However, you will only be able to disclose your business objects including all public properties as standard in very rare cases.

It is important to define contracts explicitly in an SOA scenario and to request control of the names including namespaces of your elements, the order in which the individual attributes occur, and whether or not this is absolutely necessary.

Consequently, the data contract and the service contract together serve to form a formal agreement between service and client and provide a detailed description of the structure of the data which is exchanged.

The DataContractSerializer uses the [DataContract] and [DataMember] attributes to control the exact serialization process. A [DataContract] is an opt-in model which means that only properties or member variables which have been explicitly assigned the [DataMember] attribute are serialized. The visibility of the properties is of no consequence. Listing 2-16 demonstrates the use of the [DataContract] and [DataMember] attributes. Listing 2-17 displays the resulting XSD part.

Example 2.16. [DataContract] and [DataMember] Attributes

using System;
using System.Runtime.Serialization;
using System.Xml.Serialization;

namespace Wrox.CarRentalService.Contracts
{
    [DataContract]
public class PriceCalculationRequest
    {
        [DataMember]
        public DateTime PickupDate { get; set; }
        [DataMember]
        public DateTime ReturnDate { get; set; }
        [DataMember]
        public string PickupLocation { get; set; }
        [DataMember]
        public string ReturnLocation { get; set; }
        public string Color { get; set; }
    }
}

Example 2.17. XSD Part

<xs:complexType name="PriceCalculationRequest">
    <xs:sequence>
      <xs:element minOccurs="0" name="PickupDate" type="xs:dateTime" />
      <xs:element minOccurs="0" name="PickupLocation"
nillable="true" type="xs:string" />
      <xs:element minOccurs="0" name="ReturnDate" type="xs:dateTime" />
      <xs:element minOccurs="0" name="ReturnLocation"
nillable="true" type="xs:string" />
    </xs:sequence>
  </xs:complexType>

The characteristics of the schema, which are automatically generated by WCF, include the following:

  • Namespaces: http://schemas.datacontract.org/2004/07/ + CLR and http://schemas.datacontract.org/2004/07/Wrox.CarRentalService.Contracts

  • Attributes are arranged in alphabetical order

    • <xs:element minOccurs="0" name= PickupDate "

    • <xs:element minOccurs="0" name="PickupLocation "

  • The minOccurs attribute is set to a value of 0, which means that this attribute is optional

  • The primitive CLR data types are automatically shown in the form of XSD data types. DateTime PickupDate becomes xs: dateTime

Data Contract in Detail

As you have seen from the previous examples, your data classes are typically assigned the [DataContract] attribute and the individual member variables are assigned the [DataMember] attribute.

Listing 2-18 demonstrates additional possibilities when using the [DataContract] and [DataMember] attributes to influence the generated schema.

Example 2.18. [DataContract] and [DataMember] Attributes

[DataContract(
Name="PriceCalculationRequest",                           Namespace=
"http://schemas.datacontract.org/2004/07/
Wrox.CarRentalService.Contracts")
]
public class PriceReq
{
   [DataMember(Name="PickupDate",Order=1, IsRequired=true )]
   private DateTime FromDate { get; set; }
   [DataMember(Name = "ReturnDate", Order = 3)]
   public DateTime ToDate{ get; set; }
   [DataMember( Order = 2)]
   public string PickupLocation { get; set; }
   [DataMember(Order = 4)]
   public string ReturnLocation { get; set; }
   public string CarType { get; set; }
}

Even though the original definition of the PriceCalculationRequest class has undergone significant changes, no problems occur when serializing an instance followed by comparison with the schema prepared beforehand. The [DataContract] and [DataMember] attributes create an XML info set which is valid in accordance with the contract (XSD).

To illustrate this, refer to the newly prepared schema in Listing 2-19 which is compatible with the original schema.

Example 2.19. Equivalent XSD

<xs:complexType name="PriceCalculationRequest">
    <xs:sequence>
      <xs:element minOccurs="0" name="PickupDate" type="xs:dateTime" />
      <xs:element minOccurs="0" name="PickupLocation"
           nillable="true" type="xs:string" />
      <xs:element minOccurs="0" name="ReturnDate" type="xs:dateTime" />
      <xs:element minOccurs="0" name="ReturnLocation"
           nillable="true" type="xs:string" />
    </xs:sequence>
  </xs:complexType>

The following observations are made when using the [DataContract] and [DataMember] attributes from the namespace System.Runtime.Serialization:

  • The default namespace can be overwritten by the namespace property.

  • The name property of the [DataContract] attribute serves to define the name of the complex type in XSD.

  • The access modifier of a member variable is ignored.

  • CarType does not appear as an element opt-in model — only member variables which are explicitly assigned the [DataMember] attribute are serialized.

  • If the property is IsRequired=true, the attribute is no longer optional. If this attribute is not located during deserializing, an error occurs.

  • The order can be determined explicitly with the order property.

  • The name property for the [DataMember] attribute is used to determine the name of the XSD element.

The properties of the [DataContract] and [DataMember] attributes primarily play an important role during versioning or in interop scenarios. If, for example, you have to keep a default XML schema but do not wish or are unable to make any or only minor changes to your .NET classes, you can attempt to create a compatible data contract with the attributes and properties previously mentioned.

KnownTypes

In object-oriented programming you frequently use a reference to an inherited class instead of the base class. This concept of polymorphism is frequently used, although if done in conjunction with WCF, an error occurs if special precautionary measures are not taken.

The method signature typically only contains the base class and consequently, the definition of the inherited classes is not included in the WSDL document either. This naturally also means that the inherited class for the generated proxy code is unknown and can therefore not be deserialized.

The same problem also occurs, for example, if the parameter which you use is a non-generic collection class as shown in Listing 2-20.

Example 2.20. Base Class Response Argument and ArrayList

namespace Wrox.CarRentalService.Contracts
{

    [ServiceContract(
       Namespace = "http://wrox/CarRentalService/2009/10",
       Name = "RentalService")]
    [ServiceKnownType(typeof(PriceCalculationResponseDetailed))]
    public interface ICarRentalService
    {
        [OperationContract]
        PriceCalculationResponse      CalculatePrice
        (
            PriceCalculationRequest  request
        );

        [OperationContract]
        System.Collections.ArrayList GetPrices();
    }

However, the specific implementation of the method from Listing 2-20 does not use the PriceCalculationResponse base class as a return value; it uses an inherited version named PriceCalculationResponseDetailed. Listing 2-21 shows the inherited class and the concrete service implementation.

Example 2.21. Inherited Class and Service Implementation

[DataContract]
public class PriceCalculationResponseDetailed
                 : PriceCalculationResponse
{
  [DataMember]
  public string  Currency { get; set; }
}


public PriceCalculationResponse
        CalculatePrice(PriceCalculationRequest  request)
{
   PriceCalculationResponseDetailed resp = null;
   reps=new PriceCalculationResponseDetailed();
   resp.Price = 120;
   resp.Currency = "euro";
   return resp;
}

Now, if the client proxy calls the method CalculatePrice the return is not expected and therefore produces the following error:

There was an error while trying to serialize parameter http://wrox/CarRentalService/2009/10:CalculatePriceResult. The InnerException message was 'Type 'Wrox.CarRentalService.Contracts.PriceCalculationResponseDetailed' with data contract name 'PriceCalculationResponseDetailed:http://schemas.datacontract.org/2004/07/Wrox.CarRentalService.Contracts' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types — for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.'

As the error message shows, this problem can be solved with the KnownType attribute. The KnownType attribute enables the inherited data type to be added to the data contract, thereby facilitating correct serialization and deserialization.

You should use either the [KnownType] attribute in your data class or the [ServiceKnownType] attribute in the definition of your service contract or operation contract (regardless of whether you wish to use the inherited class or the base class throughout, or whether you only wish to permit this for special service contracts or service operations).

A simple use of the [KnownType] attribute exists in enumerating all the derived classes in the data contract for the base class.

Listing 2-22 demonstrates the use of the [KnownType] attribute.

Example 2.22. KnownType on DataContract

[DataContract]
[KnownType(typeof(PriceCalculationResponseDetailed))]
public class PriceCalculationResponse
{
   [DataMember]
   public double  Price { get; set; }
}

This attribute tells the DataContractSerializer to include the inherited class in the data contract in addition to the base contract. The inheritance hierarchy is also retained in the XSD, as you can see in Listing 2-23.

Example 2.23. XSD and Inheritance

<xs:complexType name="PriceCalculationResponseDetailed">
  <xs:extension base="tns:PriceCalculationResponse">
   <xs:sequence>
    <xs:element minOccurs="0" name="Currency" type="xs:string" />
   </xs:sequence>

If you only expect to encounter an instance of an inherited class in certain methods, or if an inherited version is permitted in all the operations of a service contract, you can use the [ServiceKnownType] attribute either at service level (Listing 2-24) or at the operational level, as shown in Listing 2-25. Using the [ServiceKnownType] at the operational level means that the inherited class is only accepted for this method. Using the attribute at the service level means that every method accepts the inherited version.

Example 2.24. [ServiceKnownType] for One Method

[OperationContract]
[ServiceKnownType(typeof(PriceCalculationResponseDetailed))]
PriceCalculationResponse CalculatePrice(PriceCalculationRequest  request);

Example 2.25. [ServiceKnownType] at the Service Level

[OperationContract]
[ServiceKnownType(typeof(PriceCalculationResponseDetailed))]
PriceCalculationResponse CalculatePrice(PriceCalculationRequest  request);

Disclosing a more flexible KnownType variant involves swapping this information to the configuration file in the system.runtime.serialization section, as shown in Listing 2-26.

Example 2.26. dataContractSerializer Section in the Config File

<system.runtime.serialization>
    <dataContractSerializer>
      <declaredTypes>
        <add type="Wrox.CarRentalService.
             Contracts.PriceCalculationResponse,
             Wrox.CarRentalService.Contracts">
          <knownType type="Wrox.CarRentalService.Contracts.
             PriceCalculationResponseDetailed,
             Wrox.CarRentalService.Contracts"/>
        </add>
      </declaredTypes>
    </dataContractSerializer>
  </system.runtime.serialization>

There is a certain degree of inflexibility in telling the previous types (KnownType, ServiceKnownType, config) of WCF which inherited classes are supported. For example, every time you derive a new class, you would have to add an additional [KnownType] attribute to your base contract. Therefore, WCF offers you other possibilities for disclosing KnownTypes. Instead of specifying a fixed set of derived classes, you provide the name of a method which returns a list of derived classes. This can be done both with the [KnownType] and [ServiceType] attributes. Listing 2-27 displays the use of the [KnownType] attribute with a static method called GetTypes.

Example 2.27. Dynamic Known Types

[DataContract]
[KnownType("GetTypes")]
public class PriceCalculationResponse
{
   static Type[] GetTypes()
   {
     Type[] t = { typeof(PriceCalculationResponseDetailed) };
     return t;
   }
}

SERVICE AND DATA CONTRACT VERSIONING

Software development is particularly given to ongoing changes in requirements and, thus, in the offered functionality and structure of the data being exchanged. In principle, any minor change in your operations or data can bring about a change in the WSDL document, leading to a new version of your service. Generally speaking, however, it is neither organizationally nor technically feasible to update your service components continually and to respond to changes in the services offered.

Therefore, you will be shown techniques and processes in this chapter which permit you to maintain the exchange of compatible messages in spite of changes in the service or data contract. These changes are also regarded as changes which do not lead to a break of the contract. While the contracts do not have to be identical so-to-speak, it is imperative that they are compatible. In the case of a service contract, this would mean, for example, that the addition of a new method would not lead to incompatibilities because the client is unfamiliar with this new method anyway. The same applies in data contracts; if a new optional field is added, for example, this field is simply ignored by the DataContractSerializer and filled with a default.

However, a new contract is unavoidable in certain situations — for example, if the name of an operation is changed, or if a mandatory field is added. Changes of this type result in a break of contract, creating the need for a new version. In this case, you are advised to adapt the namespaces to the new version and to host your service at a new endpoint.

Data Contract Versioning

The WCF or the DataContractSerializer is very tolerant of changes in the structure of the data contract.

For example, it is easy to add additional data members or to leave out data members in which the IsRequired attribute is set to false. One benefit to this is that there is no need to worry about these types of minor details; however, in a service-oriented environment it is important to adhere to contracts or, where changes are necessary, to disclose them formally and officially.

Let's assume that a client makes the following changes to version 1 of the service contract/data contract shown in Listing 2-28.

Example 2.28. Service and Data Contract Version 1

[ServiceContract(
    Namespace = "http://wrox/CarRentalService/2009/10",
    Name = "RentalService")]
    public interface ICarRentalService
    {
        [OperationContract]
        PriceCalculationResponse CalculatePrice
        (
          PriceCalculationRequest  request, int priority
        );
    }

[DataContract]
public class PriceCalculationRequest
    {
        [DataMember]
        public DateTime PickupDate { get; set; }
        [DataMember]
        public DateTime ReturnDate { get; set; }
        [DataMember]
        public string PickupLocation { get; set; }
        [DataMember]
        public string ReturnLocation { get; set; }

    }

The changes to the operation and data contract in Listing 2-29 would lead to compatible contracts.

Example 2.29. Service and Data Contract Version 2

[ServiceContract(
    Namespace = "http://wrox/CarRentalService/2009/10",
    Name = "RentalService")]
public interface ICarRentalService
{
   [OperationContract]
   PriceCalculationResponse CalculatePrice
   (
     PriceCalculationRequest  request, string someData
   );
}

[DataContract]
public class PriceCalculationRequest
{
   [DataMember]
   public DateTime PickupDate { get; set; }
   [DataMember]
   public DateTime ReturnDate { get; set; }
   [DataMember]
   public string PickupLocation { get; set; }
   [DataMember]
   public string Color { get; set; }
}

The following conclusions can be drawn from the previous example:

  • Method parameters can be deleted or added

  • Data members can be deleted or added

  • Data types may be changed provided they remain compatible

  • Methods can be added

These changes would not lead to any problems of a technical nature. However, you would have to define details of procedures for dealing with missing values, for example. It is possible that a member may no longer be required and can therefore be ignored without any problems.

Round-Trip Versioning

A problem that arises again and again in connection with the versioning of data contracts is the newly added attribute in which deserialization to an old data contract is ignored.

Let's take a closer look at the original example relating to the price inquiry — the client sends a price inquiry to the service and receives a reply object including a flag and a price. The client can confirm the offered price by calling the ConfirmPrice method again, as shown in Listing 2-30. Listing 2-31 displays the service contract and data contract.

Example 2.30. ConfirmPrice Service Contract

[ServiceContract(
   Namespace = "http://wrox/CarRentalService/2009/10",
   Name = "RentalService")]
public interface ICarRentalService
{
[OperationContract]
PriceCalculationResponse CalculatePrice
   (
     PriceCalculationRequest  request
   );
[OperationContract]
bool ConfirmPrice(PriceCalculationResponse resp);
}
[DataContract]
public class PriceCalculationResponse
{
  [DataMember]
  public int Flag { get; set; }
  [DataMember]
  public string  Price { get; set; }
}

Example 2.31. ConfirmPrice Client Code

using (RentalServiceClient carRentalClient = new RentalServiceClient())
{
   PriceCalculationRequest req = new PriceCalculationRequest();
   req.PickupDate  = System.DateTime.Now.AddDays(5);
   req.ReturnDate  = System.DateTime.Now.AddDays(7);
   req.PickupLocation = "Graz";
   req.ReturnLocation = "Villach";
   PriceCalculationResponse resp;
   resp = carRentalClient.CalculatePrice(req);
   Console.WriteLine("Price {0}", resp.Price);
   Console.WriteLine(carRentalClient.ConfirmPrice(resp));
}

During internationalization, add the Currency attribute to the data contract.

Your service can still use the client code because the additional field does not lead to any problems if the IsRequired property does not apply. However, the value of the Currency attribute is lost on the client side because the client is unfamiliar with this newly added attribute and is also unable to deserialize its content.

IExtensibleDataObject

The solution to this round-tripping problem is to implement the IExtensibleDataObject interface and the requisite ExtensionDataObject field. If the DataContractSerializer detects unknown elements in the XML document, they are written to the ExtensionDataObject property bag during deserialization. The content of the ExtensionDataObject is retained when further use is made of this object, resulting in no data loss between different versions of data contracts.

If you use the Add Service Reference dialog box, the data classes on the client side automatically implement this interface. If you wish to reciprocate on the server side, you have to implement the interface manually in the data classes — the code in Listing 2-32 is sufficient for this.

Example 2.32. IExtensibleDataObject

[DataContract]
public class PriceCalculationResponse :IExtensibleDataObject
{
   public ExtensionDataObject ExtensionData { get; set; }

   [DataMember]
   public int Flag { get; set; }
   [DataMember]
   public double   Price { get; set; }
   [DataMember]
   public string Currency { get; set; }
}

Note

You can also prevent ExtensionData in general by setting a service behavior in your configuration file with the property <dataContractSerializer ignoreExtensionDataObject="true"/>. You should take particular care when working with extension data on the server side because it may cause a security gap.

Best Practices of Service Contract Versioning

If precise compliance between schemas is required, you should assign a new version to your contract every time a change is implemented.

If there is no need for precise compliance between schemas, you should heed the following points:

  • You can add new methods at any time

  • You may not delete any existing methods

  • The parameter data types must remain compatible

Best Practices of Data Contract Versioning

If precise compliance between schemas is required, you should assign a new version to your contract every time a change is implemented.

If there is no need for precise compliance between schemas, you should heed the following points:

  • Data contracts should not be assigned new versions as a result of inheritance. Create a new independent data class instead.

  • To facilitate round-tripping, you should implement the IExtensibleDataObject interface at the start.

  • If you have to change the name of your data class or of some members, compatible data contracts can be created using the DataContract or DataMember attributes.

  • Do not make subsequent changes to your data types.

  • Do not change the order in which your data members appear by using the property [DataMember(Order=?)].

  • Leave the default IsRequired value (false) unchanged.

  • You can add additional data members at any time but you should remember that this would change the serialization order. This problem is avoided by setting the Order property for new members to the current version's value. Therefore, data members added in version 2 should be assigned Order=2.

  • Data members should not be deleted.

  • Subsequent changes should not be made to the IsRequired property.

Listing 2-33 displays some of the recommended practices.

Example 2.33. Best Practices of Versioning

[DataContract(
   Name="PriceCalculationRequest",
   Namespace="http://schemas.datacontract.org/2004/07/
   Wrox.CarRentalService.Contracts")]
public class PriceReq //Version 1
{
   [DataMember()]
   private DateTime PickupDate { get; set; }
   [DataMember()]
   public DateTime ReturnDate { get; set; }
   [DataMember()]
   public string PickupLocation { get; set; }
   [DataMember()]
   public string ReturnLocation { get; set; }
}

[DataContract(
   Name = "PriceCalculationRequest",
   Namespace = "http://schemas.datacontract.org/2004/07/
   Wrox.CarRentalService.Contracts")]
public class PriceReq //Version 2
{
   [DataMember(Name = "PickupDate")]
   private DateTime FromDate { get; set; }
   [DataMember(Name = "ReturnDate")]
   public DateTime ToDate { get; set; }
   [DataMember(Order = 2)]
   public string PickupLocation { get; set; }
   [DataMember()]
   public string ReturnLocation { get; set; }
   [DataMember(Order=2)] //from Version 2
   public string CarType { get; set; }
}

MESSAGE CONTRACTS

At the start of this chapter, it was explained that SOAP messages are exchanged between the client and the server. However, you have only been able to influence the content of the SOAP body up to now. In most cases, you will be able to manage with data contracts and operation contracts, which is recommended. However, in certain situations you may have to gain total control of the entire SOAP message (header and body). The SOAP header is a suitable method for transferring additional data without extending the data class (for example, a security token).

Message contracts give you complete control over the content of the SOAP header, as well as the structure of the SOAP body. Up to now you have used data contracts as transfer or return parameters — these are now simply replaced by message contracts. If you opt for message contracts, it is important to use message contracts for all the parameters — methods cannot contain a mixture of data contracts and message contracts.

Listing 2-34 shows how the three attributes [MessageContract], [MessageHeader], and [MessageBody] are used. In addition to the price request object, a username is also transferred in the SOAP header.

Example 2.34. [MessageContract], [MessageHeader], and [MessageBody]

[ServiceContract(
   Namespace = "http://wrox/CarRentalService/2009/10",
   Name = "RentalService")]
public interface ICarRentalService
{
   [OperationContract]
   PriceCalculationResp  CalculatePrice(PriceCalculation  request);
}

[MessageContract]
public class PriceCalculation
{
   [MessageHeader]
public CustomHeader SoapHeader { get; set; }

   [MessageBodyMember]
   public PriceCalculationRequest  PriceRequest { get; set; }
}

[DataContract]
public class CustomHeader
{
   [DataMember]
   public string Username { get; set; }
}

[DataContract]
public class PriceCalculationRequest
{
   [DataMember]
   public DateTime PickupDate { get; set; }
   [DataMember]
   public DateTime ReturnDate { get; set; }
   [DataMember]
   public string PickupLocation { get; set; }
   [DataMember]
   public string ReturnLocation { get; set; }
     }

Listing 2-35 illustrates the usage on the client side and Listing 2-36 shows the SOAP message.

Example 2.35. Message Contracts — Client Side

using (RentalServiceClient carRentalClient = new RentalServiceClient())
{
   PriceCalculation calc = new PriceCalculation();
   PriceCalculationRequest req = new PriceCalculationRequest();
   req.PickupDate = System.DateTime.Now.AddDays(5);
   req.ReturnDate = System.DateTime.Now.AddDays(7);
   req.PickupLocation = "Graz";
   req.ReturnLocation = "Villach";
   calc.PriceRequest = req;

   CustomHeader sHeader = new CustomHeader();
   sHeader.Username = "Johann";
   calc.SoapHeader = sHeader;

   PriceCalculationResp  resp=  carRentalClient.CalculatePrice(calc);
}

Example 2.36. SOAP and Message Headers

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
 <s:Header>
  <h:SoapHeader xmlns:h="http://wrox/CarRentalService/2009/10"
    xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
    <Username xmlns="http://schemas.datacontract.org/2004/07/
     Wrox.CarRentalService.Contracts">
      Johann
    </Username>
  </h:SoapHeader>
 </s:Header>
<s:Body>
 <PriceCalculation xmlns="http://wrox/CarRentalService/2009/10">
  <PriceRequest
   xmlns:a=
"http://schemas.datacontract.org/2004/07/
Wrox.CarRentalService.Contracts"
   xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
   <a:PickupDate>2010-01-15T14:15:08.1683905+01:00</a:PickupDate>
   <a:PickupLocation>Graz</a:PickupLocation>
   <a:ReturnDate>2010-01-17T14:15:08.1693671+01:00</a:ReturnDate>
   <a:ReturnLocation>Villach</a:ReturnLocation>
  </PriceRequest>
 </PriceCalculation>
</s:Body>
</s:Envelope>

You can find other specific examples of how message contracts are used in Chapter 7.

XML Serialization

As already mentioned in the introduction, WCF supports a number of types of serialization. By default, WCF uses the DataContractSerializer class to serialize your CLR objects. In the previous examples you have also seen the use of different properties in connection with the [DataContract] and [DataMember] attributes. To an extent, you can control the structure of the generated XML info set.

The DataContractSerializer is optimized for performance, although it offers fewer possibilities as far as influencing the structure of the created document is concerned. If the possibilities offered do not meet your needs, you can switch to the XML Serializer. The XML Serializer also offers you more control in interop scenarios or in communication with old ASP.NET Web Services.

To instruct WCF to use the XML Serializer, you can simply assign your service contract the [XmlSerializerFormat] attribute. However, should you only wish to use the XML Serializer for certain operations, you can also apply this attribute to your methods. Note that the XML Serializer also serializes all public properties by default. You can find further information about these classes in the Framework Documentation on MSDN.

Listing 2-37 demonstrates the use of the [XMLSerializerFormat] with attributes from the System.XML.Serialization namespace and Listing 2-38 displays the XSD for the PriceCalculationRequest type.

Example 2.37. XML Serialization

[XmlSerializerFormat]
[ServiceContract(
   Namespace = "http://wrox/CarRentalService/2009/10",
   Name = "RentalService")]
public interface ICarRentalService
{
   [OperationContract]
   PriceCalculationResponse CalculatePrice
   (
    PriceCalculationRequest request
   );
}

[XmlRoot("PriceRequest")]
public class PriceCalculationRequest
{
   [XmlAttribute()]
   public  string PickupLocation { get; set; }
   [XmlElement(ElementName="ReturnLoc")]
   public string ReturnLocation { get; set; }
   [XmlIgnore()]
   public DateTime PickupDate { get; set; }
   private  DateTime ReturnDate { get; set; }
}

Example 2.38. XSD for the PriceCalculationRequest Type

<xs:element name="CalculatePrice">
 <xs:complexType>
  <xs:sequence>
<xs:element minOccurs="0" maxOccurs="1"
   name="request"
   type="tns:PriceCalculationRequest" />
</xs:sequence>

</xs:complexType>
</xs:element>
<xs:complexType name="PriceCalculationRequest">
 <xs:sequence>
  <xs:element minOccurs="0" maxOccurs="1" name="ReturnLoc"
    type="xs:string" />
  </xs:sequence>
 <xs:attribute name="PickupLocation" type="xs:string" />
</xs:complexType>
<xs:element name="CalculatePriceResponse">
 <xs:complexType>
  <xs:sequence>
   <xs:element minOccurs="0" maxOccurs="1"
name="CalculatePriceResult"
   type="tns:PriceCalculationResponse" />
  </xs:sequence>
 </xs:complexType>
</xs:element>
...
</xs:complexType>
..................Content has been hidden....................

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