Data Contracts & Serialization

Summary

Introduction

WCF Services and their clients depend on the exchange of data to accomplish any task. This section discusses how WCF handles data and data serialization.

In general, a WCF service receives messages, processes them and sends messages back. For example, the following service contract accepts a message with information on the project name and returns a message that contains the project id:

[ServiceContract]
public interface IProject
{
    [OperationContract]
    int GetProjectID( string projectname )


    [OperationContract]
    void GetProjectDetails(int ID, out string manager, out DateTime dtStartDate, out DateTime dtEndDate);
}

Service Contracts

Describing Messages

This section explains the various ways in which messages can be described by an operation contract:

Describing Messages Using Parameters

This is the simplest way and was illustrated in the example above. Here a message is described using parameters and a return value. If a return value is not enough to describe a reply message, out-parameters may be used, again as was illustrated in the example above. Reference parameters (as opposed to value parameters) may also be used to make a parameter part of both request and reply.

When describing messages using parameters, the parameters must be types that can be serialized, i.e., converted to XML. By default, WCF uses a component called the DataContractSerializer to perform this conversion. Most primitive types are supported. User-defined data types must have a data contract. See Data Contracts section below.

If DataContractSerializer is not adequate to serialize your types (When?), then you can use an alternative but familiar WCF serialization engine; XmlSerializer. If you have used XmlSerializer before then you know that it allows you more control over the resultant XML using attributes such as [XmlAttribute] and [XmlElement]. To switch to using XmnlSerializer, on the service or the operation level, apply the [XmlSerializerFormat] attribute:

[ServiceContract]
public interface IProject
{
    [OperationContract]
    int GetProjectID( string projectname )

    [OperationContract]
    [XmlSerializerFormat]
   
void GetProjectDetails(int ID, out string manager, out DateTime dtStartDate, out DateTime dtEndDate);
}

Describing Empty Messages

An empty request message is described by having no input or reference parameters. While an empty reply message is described by having a void return type and no output or reference parameters. For example:

[ServiceContract]
public interface IProject
{
    [OperationContract]
    bool Initialize();         // Empty request but not empty reply

    [OperationContract]
    void ClearProject(int ID); // Empty reply but not empty request

    [OperationContract]
    void ClearAllProjects();   // Empty reply and empty request

    [OperationContract(IsOneWay=true)]
    void SetProjectStatus(int ID);     // Unlike above operations, a one-way operation cannot return a fault condition
}

Describing Messages Using Message Contracts

In this approach, the request message is represented with a single type, and the reply message is also represented with a single type. While you can use a data contract for this purpose, the recommended approach is to use a message contract. Also message contracts allow you to exercise more control over resultant messages by deciding which pieces of information should be in the message body and which should be in the message headers. The following example illustrates:

[ServiceContract]
public interface IReport
{
    [OperationContract]
    ReportResponse RunReport(ReportRequest request)
}

[MessageContract]
public class ReportRequest
{
    [MessageHeader] public DateTime date;
    [MessageBodyMember] public string name;
}

[MessageContract]
public class ReportResponse
{
    [MessageBodyMember] public int ID
    [MessageBodyMember] public ReportDetails report;
}

[DataContract]
public class ReportDetails
{
    [DataMember] public string author
    [DataMember] public string text;
}

Describing Messages Using Streams

A Stream-derived class can also be used in an operation contract or as a message-contract body-member (must be the only member in this case). The following illustrates how to use Stream to describe messages:

[ServiceContract]
public interface IFile
{
    // Note that return type is Stream. No Stream-derived classes can be used
    [OperationContract]
    public Stream DownloadFile( string filename );

    // INCORRECT: Stream and non-Stream data cannot be used in a single message body.
    // Instead used a message contract to put the extra data in message headers. See
    // example below
    [OperationContract]
    public void UploadFile(string filename, Stream filedata);

    // The correct implementation of the above method
   
[OperationContract]
    public void UploadFile2( FileMessage message );
}

[MessageContract]
public class FileMessage
{
    [MessageHeader] public string filename
    [MessageBodyMember] public Stream fileData
}

Describing Messages Using Faults

In addition to describing return value and output/reference parameters, any operation that is not One-Way can also return a fault message if the implementation of the service operation fails for any reason. See Faults for more details.

Describing Messages Using Derived Types

You may want to use a base in an operation-contract or A message-contract, and then use a derived type when actually invoking the operation. You can either use [ServiceKnownType] attribute or [KnownType] attributes. The following example illustrates:

[ServiceContract]
public interface IEmployee
{
    // The following assumes that Manager and Developer types derive from the Employee type
    [OperationContract]
    [ServiceKnownType(typeof(Manager))]
    [ServiceKnownType(typeof(Developer))]
    public SetDetails(Employee emp);
}

An alternative implementation that uses message contracts is:

[ServiceContract]
public interface IEmployee
{
    [OperationContract]
    public SetDetails(Employee emp);
}

[MessageContract]
[KnownType(typeof(Manager))]
[KnownType(typeof(Developer))]
public class Employee
{
    // Employee details
}

Controlling Serialization Process

There are a number of in which you can customize the way data is serialized:

When the default DataContractSerializer is in use, there are some aspects of the serialization process that may be controlled by applying the [ServiceBehaviorAttribute] attribute to the service. Specifically, the [MaxItemsInObjectGraph] property may be used to set the quota that limits the maximum number of objects the DataContractSerializer deserializes:

[ServiceContract]
[ServiceBehavior(MaxItemsInObjectGraph=100000)]
public interface IDataService
{
    [OperationContract] DataPoint[] GetData();
}

Serialization Behaviors

There are two behaviors available in WCF, the DataContractSerializerOperationBehavior and the XmlSerializerOperationBehavior that are automatically plugged in depending on which serializer is in use for a particular operation. Because these behaviors are applied automatically, you normally do not have to be aware of these behaviors. However, the DataContractSerializerOperationBehavior has the MaxItemsInObjectGraph, IgnoreExtensionDataObject and DataContractSurrogate properties that may be used to customize the serialization process. The first two properties have the same meaning as discussed in the previous section. The DataContractSurrogate property can be used to enable data contract surrogates, which are a powerful mechanism to customize and extend the serialization process. For more information, see Data Contract Surrogates in MSD.

The other approach in which you can customize the way data is serialized is to control MaxItemsInObjectGraph and IgnoreExtensionDataObject through configuration, using the DataContractSerializer endpoint or service behavior, as shown in the following example.

<configuration>
    <system.serviceModel>
        <behaviors>
            <endpointBehaviors>
                <behavior name="LargeQuotaBehavior">
                    <dataContractSerializer maxItemsInObjectGraph="100000" />
                </behavior>
            </endpointBehaviors>
        </behaviors>

        <client>
            <endpoint address=http://example.com/myservice
                      binding="basicHttpBinding" bindingConfiguration=""
                      contract="IDataService"
                      behaviorConfiguration="LargeQuotaBehavior" name="" />
        </client>
    </system.serviceModel>
</configuration>

See MSDN for more details

Data Contracts

User-defined types must define a data contract for them to be serializable. This is done by applying the [DataContract] attribute to classes, structs and enums. [DataMember] attribute must then be applied to each member to indicate that it is part of the contract, i.e., it should be serialized/derserialized. The following example illustrates:
 

[ServiceContract]
public interface ISomeInterface
{
    // No data contract is required since parameter and return types are primitive types.
    [OperationContract]
    double SquareRoot(int root);


    // No Data Contract required because parameter and return types are NET types both
    // marked with the [Serializable] attribute (see class descriptions in MSDN for
    // both Bitmap and Uri)
    [OperationContract]
    System.Drawing.Bitmap GetPicture(System.Uri pictureUri);

    // The MyTypes.PurchaseOrder is a user-defined type, and thus  requires a data contract.
    [OperationContract]
    bool ApprovePurchaseOrder(MyTypes.PurchaseOrder po);
}

namespace MyTypes
{
    // For this contract to be valid, all its members annotated with [DataMember] must be serializable
    // Apply [DataMember] to the property. [DataMember] can be applied to field, properties and events.
    // Anything that is not annotated with [DataMember] is not serialized.

    [DataContract]
    public class PurchaseOrder
    {
        // This member is not serialized, but its property PurchaseOrderId is.
        private int _id;

        // This member is not serialized because [DataMember] as not applied
        private float fSum;

        // The following two members are serialized
        [DataMember]
        internal string strCustomerName;

        [DataMember]
        private DateTime dtOrderDate;

        [DataMember]
        public int PurchaseOrderId
        {
            // During serialization, the get code is called to get the value of the properties being serialized
            get { return _id}

            // During deserialization, the set code is called to set the properties to the value being deserialized
            set { _id= value; }

        }
    }
}

Note the following rules regarding Data Contract names:

The following example, along with comments, illustrate some of the basic concepts relating to data contract names:

// To control the data contract namespace for each type, set the Namespace
// property on [DataContract]. Alternatively, you can set the data contract namespace
// for the entire assembly by using the [ContractNamespace] attribute:
// The contract name is 'User' instead of 'MyUser'. The namespace is
// 'www.diranieh.com/contracts' instead of the default
// "http://schemas.datacontract.org/2004/07/Clr.Namespace"

[DataContract(Name="User", Namespace="www.diranieh.com/contracts")]
public class MyUser
{
    string firstName;
    string lastName;

    // This data member is named age default
    [DataMember]
    private int age;

    // The name of this data member is overridden to become DateOfBirth
    [DataMember(Name=DateOfBirth)]
    private DateTime DOB;


    [DataMember(Name="FirstName")]
    public string Name1
    {
        get { return firstName; }
        set { firstName = value; }
    }

    [DataMember(Name="LastName")]
    public string Name2
    {
        get { return lastName; }
        set { lastName = value; }
    }
}

Data Contract Names for Generic Types

There are special rules for determining data contract names for generic types. This is to avoid data contract name collisions between two closed generics of the same generic type.By default, the data contract name for a generic type is the name of the type, followed by the string "Of", followed by the data contract names of the generic parameters, followed by a hash computed using the data contract namespaces of the generic parameters. When all of the generic parameters are primitive types, the hash is omitted. For example:

[DataContract(Namespace = "urn:shapes")]
public class Square
{
    // Code not shown.
}

[DataContract(Name = "RedBrush", Namespace = "urn:default")]
public class RegularRedBrush
{
    // Code not shown.
}

[DataContract(Name = "RedBrush", Namespace = "urn:special")]
public class SpecialRedBrush
{
    // Code not shown.
}

// the type Drawing<Square,RegularRedBrush> has the data contract name
// "DrawingOfSquareRedBrush5HWGAU6h", where "5HWGAU6h" is a hash of the
// "urn:shapes" and "urn:default" namespaces.
//
// The type Drawing<Square,SpecialRedBrush> has the data contract name
// "DrawingOfSquareRedBrushjpB5LgQ_S", where "jpB5LgQ_S" is a hash of
// the "urn:shapes" and the "urn:special" namespaces. Note that if the
// hash is not used, the two names would have been
// identical, and thus a name collision would have occurred.
[DataContract]
public class Drawing<Shape, Brush>
{
    // Code not shown.
}

If the data contract names generated for generic types are unacceptable, you can use the Name property of the DataContractAttribute attribute to specify a different way to generate names. For example, the preceding generic Drawing type could have been declared as

// Numbers in curly braces inside of the Name property can be used to refer
// to data contract names of the generic parameters. (0 refers to the first
// parameter, 1 refers to the second, and so on.) A "#" (pound) symbol
// inside curly braces can be used to refer to the hash. Each of these
// references can be used multiple times or may not be used at all.
[
DataContract(Name = "Drawing_using_{1}_brush_and_{0}_shape")]
public class Drawing<Shape, Brush>
{
    // Code not shown.
}

In this case, the type Drawing<Square,RegularRedBrush> has the data contract name "Drawing_using_RedBrush_brush_and_Square_shape". Note that because there is a "{#}" in the Name property, the hash is not a part of the name, and thus the type is susceptible to naming collisions; for example, the type Drawing<Square,SpecialRedBrush> would have exactly the same data contract name.

Data Contract Equivalence

Types sent between a client and a service do not necessarily have to exist on the receiving end. The only requirement is that data contracts of both types are equivalent:

For example, the following data contracts are equivalent – both data contracts have the same name, and each data member in class Customer have an equivalent data member in class Person:

[DataContract]
public class Customer
{
    [DataMember]
    public string fullName;

    [DataMember]
    public string telephoneNumber;
}

[DataContract(Name = "Customer")]
public class Person
{
    [DataMember(Name = "fullName")]
    private string nameOfPerson;                // Equivalent to Customer.fullName

    private string address;
    [DataMember(Name = "telephoneNumber")]    // Equivalent to Customer.telephoneNumber 

    private string phoneNumber;
}

Data Member Order

In some applications, you may need to know the order in which data from the various data members is sent or is expected to be received (such as the order in which data appears in the serialized XML). Sometimes it may be necessary to change this order. Note the following basic rules regarding ordering of data members:

Consider the following code:

[DataContract]
public class BaseType
{
    [DataMember]
    public string zebra;
}

[DataContract]
public class DerivedType : BaseType
{
    [DataMember(Order = 0)] public string bird;
    [DataMember(Order = 1)] public string parrot;
    [DataMember] public string dog;
    [DataMember(Order = 3)] public string antelope;
    [DataMember] public string cat;
    [DataMember(Order = 1)] public string albatross;
}

The XML will look similar to the following:

<DerivedType>
    <!-- Zebra is a base data member, and appears first. -->
    <zebra/>

    <!-- Cat has no Order, appears alphabetically first. -->
    <cat/>

    <!-- Dog has no Order, appears alphabetically last. -->
    <dog/>

    <!-- Bird is the member with the smallest Order value -->
    <bird/>

    <!-- Albatross has the next Order value, alphabetically first. -->
    <albatross/>

    <!-- Parrot, with the next Order value, alphabetically last. -->
    <parrot/>

    <!-- Antelope is the member with the highest Order value. Note that
    Order=2 is skipped -->
    <antelope/>

</DerivedType>

Data Contract Known Types

The [KnownType] attribute is used to inform the deserialization engine about types that should be considered during the actual deserialization. When passing parameters and return values between a client and a service, both endpoints share fully all of the data contracts of the data to be transmitted. However, there are circumstances in which this is not the case:

Some types, including .NET Framework types, have members that fall under one of the preceding three categories. For example, Hashtable uses Object to store the actual objects in the hash table. When serializing these types, the receiving end again cannot determine in advance the data contract for these members.

When data arrives at a receiving endpoint, the WCF runtime attempts to deserialize the data into an instance of some CLR type. The type that is instantiated for deserialization is chosen by first inspecting the incoming message to determine the data contract. The deserialization engine then attempts to find a CLR type that implements a data contract compatible with the message contents. The set of candidate types that the deserialization engine considers during this process is referred to as the deserializer's set of "known types."

One way to let the deserialization engine know about a type is by using the [KnownType]. The attribute can only be applied to whole data contract types. The attribute is applied to an "outer type" that can be a class or a structure. In its simplest usage, applying the attribute simply specifies a type as a "known type." More than one [KnownType] attribute can be applied to the same type.

Primitive types and certain types treated as primitives (for example, DateTime and XmlElement) are always "known" and never have to be added through the [KnownType] attribute. However, arrays of primitive types have to be added explicitly. For example, consider these three classes with an inheritance relationship:

[DataContract]
public class Person { }

[DataContract(Name = "Manager")]
public class ManagerType : Person { }

[DataContract(Name = "Developer")]
public class DeveloperType : Person { }

The following DeptInfo class can be serialized, but cannot be deserialized if the person member is set to either a Manager or Developer class, because the deserialization engine does not recognize any types with data contract names "Manager" or "Developer":

[DataContract]
public class DeptInfo
{
    [DataMember] private Person person;
}

The correct way to write the DeptInfo type is as follows:

[DataContract]
[KnownType(typeof(Manager))]
[KnownType(typeof(Employee))]
public class DeptInfo
{
    [DataMember] private Person person;
}

In the following example, even though both CustomerTypeA and CustomerTypeB have the Customer data contract, an instance of CustomerTypeB is created whenever a PurchaseOrder is deserialized, because only CustomerTypeB is known to the deserialization engine:

public interface ICustomerInfo
{
    string ReturnCustomerName();
}

[DataContract(Name = "Customer")]
public class CustomerTypeA : ICustomerInfo
{
    public string ReturnCustomerName()
    {
        return "no name";
    }
}

[DataContract(Name = "Customer")]
public class CustomerTypeB : ICustomerInfo
{
    public string ReturnCustomerName()
    {
        return "no name";
    }
}

[DataContract]
[KnownType(typeof(CustomerTypeB))]
public class PurchaseOrder
{
    [DataMember]
    ICustomerInfo buyer;

    [DataMember]
    int amount;
}

Note that there are additional ways to add known types: You can also add types to the KnownTypeCollection, accessed through the KnownTypes property of the DataContractSerializer. Additionally, known types may be added through a configuration file. This is useful when you do not have control over the type that requires known types for proper deserialization, such as when using third-party type libraries with Windows Communication Foundation (WCF). For more information, see the reference documentation for the <system.runtime.serialization> configuration section

Note: See MSDN for notes on known types and generic methods.

Data Contract Versioning

Changes to a data contract can be breaking or nonbreaking. When a data contract is changed in a nonbreaking way, both the old and the new versions of the data contract can still communicate. On the other hand, a breaking change prevents communication in one or both directions. Changes to a type that do not affect how it is transmitted and received are nonbreaking. For example, you can change the name of a field in a nonbreaking way if you then set the Name property of the [DataMember] to the older version name. For example, consider version 1 of this data contract:

// Version 1
[DataContract]
public class Person
{
    [DataMember]
    private string Phone;
}

The following code shows a nonbreaking change:

// Version 2. This is a non-breaking change because the data contract
// has not changed, even though the type has.
[DataContract]
public class Person
{
    [DataMember(Name = "Phone")]
    private string Telephone;
}

Note that the following changes are always breaking:

Adding/Removing Data Members

In most cases, adding or removing a data member is a non-breaking change, unless you require strict schema validity. When a type with an extra field is deserialized into a type with a missing field, the extra information is ignored. When a type with a missing field is deserialized into a type with an extra field, the extra field is left at its default value, usually zero or null.

Required Data Members

A data member may be marked required by setting the IsRequired property of the [DataMember] to true. If required data is missing while deserializing, an exception is thrown instead of setting the data member to its default value.

Adding a required data member is a breaking change. That is, the newer type can still be sent to endpoints with the older type, but not the other way around. Removing a data member that was marked as required in any prior version is also a breaking change. Changing the IsRequired property value from true to false is not breaking, but changing it from false to true may be breaking if any prior versions of the type do not have the data member in question.

Data Contract Serializer

Recall that the default serializer used by WCF is DataContractSerializer. The DataContractSerializer translates between .NET Framework objects and XML, in both directions. The following sections explain how this serializer works. When serializing .NET Framework objects, the serializer understands a variety of serialization programming models, including the new Data Contract model. When deserializing XML, the serializer uses the XmlReader and XmlWriter classes.

WCF also includes a companion serializer, the NetDataContractSerializer. The NetDataContractSerializer is similar to the BinaryFormatter and SoapFormatter serializers because it also emits .NET type names as part of the serialized data. It is used when exactly the same types are shared on the serializing and the deserializing ends. Both the DataContractSerializer and the NetDataContractSerializer derive from a common base class, the XmlObjectSerializer.

Using DataContractSerializer

A serializer created for a certain root type cannot be used to serialize (or deserialize) another type, unless the type is derived from the root type. The following example shows two classes:

[DataContract]
public class Person

{
    ...
}


[DataContract]
public class PurchaseOrder
{ ... }

//This can now be used to serialize/deserialize Person but not PurchaseOrder.
DataContractSerializer dcs = new DataContractSerializer(typeof(Person));

If there is any polymorphism involved in the types being serialized that is not already handled using the [KnownType] attribute or some other mechanism, a list of possible known types must be passed to the serializer’s constructor using the knownTypes parameter:

//Create a serializer for the inherited types using the knownType parameter.
Type[] knownTypes = new Type[] { typeof(Book), typeof(Newspaper) };

// All types are known after construction
DataContractSerializer dcs = new DataContractSerializer(typeof(LibraryPatron), knownTypes);

Normally, when an object is serialized, the default name and namespace of the outermost XML element are determined according to the data contract name and namespace. The names of all inner elements are determined from data member names, and their namespace is the data contract’s namespace. The following example sets Name and Namespace values in the constructors of the [DataContract] and [DataMember] classes:

[DataContract(Name = "PersonContract", Namespace = "http://schemas.contoso.com")]
public class Person2
{
    [DataMember(Name = "AddressMember")]
    ublic Address theAddress;
}

[DataContract(Name = "AddressContract", Namespace = "http://schemas.contoso.com")]
public class Address
{
    [DataMember(Name = "StreetMember")]
    public string street;
}

<!-- Serializing an instance of the Person class would produce XML similar to the following -->
<PersonContract xmlns="http://schemas.contoso.com">
    <AddressMember>
        <StreetMember>123 Main Street</StreetMember>
    </AddressMember>
</PersonContract>

However, you can customize the default name and namespace of the root element by passing the values of the rootName and rootNamespace parameters to the DataContractSerializer constructor. Note that the rootNamespace does not affect the namespace of the contained elements corresponding to data members. It affects only the namespace of the outermost element.

Object Graph Preservation

Consider the following data contracts:

[DataContract]
public class PurchaseOrder
{
    [DataMember]
    public Address billTo;

    [DataMember]
    public Address shipTo;
}

[DataContract]
public class Address
{
    [DataMember]
    public string street;
}
 

//Construct a purchase order:
Address adr = new Address();
adr.street = "123 Main St.";
PurchaseOrder po = new PurchaseOrder();
po.billTo = adr;
po.shipTo = adr;

Notice that billTo and shipTo fields are set to the same object instance. However, the generated XML will have the information duplicated, and will look similar to the following XML:

<PurchaseOrder>
    <billTo><street>123 Main St.</street></billTo>
    <shipTo><street>123 Main St.</street></shipTo>
</PurchaseOrder>

Note that replicating data may be inefficient, and sometimes it is important to preserve the fact that two references are to the same object, and not to two identical objects. For these reasons, some DataContractSerializer constructor overloads have a preserveObjectReferences parameter (the default is false). When this parameter is set to true, a special method of encoding object references, understood only by WCF, will be used. When set to true, the XML snippet would now resemble this:

<PurchaseOrder ser:id="1">
    <billTo ser:id="2"><street ser:id="3">123 Main St.</street></billTo>
    <shipTo ser:ref="2"/>
</PurchaseOrder>

Note that the XML produced by the DataContractSerializer with preserveObjectReferences set to true is not interoperable with any other technologies, and can be consumed only by another DataContractSerializer instance, also with preserveObjectReferences set to true.

Serialization

The simplest way to serialize an object with DataContractSerializer is to pass it to the WriteObject method:

Person p = new Person();
DataContractSerializer dcs = new DataContractSerializer(typeof(Person));
XmlDictionaryWriter xdw = XmlDictionaryWriter.CreateTextWriter(someStream,Encoding.UTF8 );
dcs.WriteObject(xdw, p);

<!-- Following XML is produced -->
<Person>
    <Name>Jay Hamlin</Name>
    <Address>123 Main St.</Address>
</Person>

You can do step-by-step serialization by using the WriteStartObject, WriteObjectContent and WriteEndObject methods to write the start element, write the object contents, and close the wrapper element, respectively. For example, you can insert contents like attributes or comments between WriteStartObject and WriteObjectContent:

dcs.WriteStartObject(xdw, p);
xdw.WriteAttributeString("serializedBy", "myCode");
dcs.WriteObjectContent(xdw, p);
dcs.WriteEndObject(xdw);

This would produce XML similar to the following:

<Person serializedBy="myCode">
    <Name>Jay Hamlin</Name>
    <Address>123 Main St.</Address>
</Person>

Deserialization

The simplest way to deserialize an object is to call one of the ReadObject method overloads. Also note that the object returned by the ReadObject method must be cast to the appropriate type:

DataContractSerializer dcs = new DataContractSerializer(typeof(Person));
FileStream fs = new FileStream(path, FileMode.Open);
XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas());
Person p = (Person)dcs.ReadObject(reader);

Using NetDataContractSerializaer

The main difference between the DataContractSerializer and the NetDataContractSerializer is that the DataContractSerializer uses data contract names, whereas the NetDataContractSerializer outputs full .NET assembly and type names in the serialized XML. This means that the exact same types need to be shared between the serialization and deserialization endpoints. This means that the known types mechanism is not needed with the NetDataContractSerializer because the exact types to be deserialized are always known.

Using the NetDataContractSerializer is very similar to using the DataContractSerializer, with the following differences:

Schema Import and Export

Recall that DataContractSerializer translates between .NET objects and XML (in both directions). WCF also includes associated schema import and schema export mechanisms:

Use the XsdDataContractExporter class when you have created a Web service that incorporates data represented by common language runtime (CLR) types and when you need to export XML schemas for each type to be consumed by other Web services. The schemas can then be exposed through a Web Services Description Language (WSDL) document for use by others who need to interoperate with your service.

Conversely, if you are creating a Web service that must interoperate with an existing Web service, use the XsdDataContractImporter to transform XML schemas and create the CLR types that represent the data in a selected programming language (See MSDN for full details.)

XmlSerializer

Recall that Windows Communication Foundation (WCF) can use two different serialization technologies to turn the data in your application into XML that is transmitted between clients and services, a process called serialization. By default WCF uses the DataContractSerializer class to serialize data types. This serializer supports the following types:

WCF also supports the XmlSerializer. The XmlSerializer is not unique to WCF; it is the same serialization engine as the one used by ASP.NET Web Services. The XmlSerializer supports a much narrower set of types than the DataContractSerializer, but allows much more control over the resulting XML and supports much more of the XML Schema (XSD) standard, it also does not require any declarative attributes on the serializable types. The XmlSerializer does not support data contract types.

When using Svcutil.exe or the Add Service Reference feature in Microsoft Visual Studio to generate client code for a 3rd-party service, or to consume a 3rd-party schema, an appropriate serializer is selected for you automatically: If the schema is not compatible with the DataContractSerializer, the XmlSerializer is selected.

There may be times when you must manually switch to the XmlSerializer. For example, When migrating an application from ASP.NET Web Services to WCF, you may wish to reuse existing, XmlSerializer-compatible types instead of creating new data contract types. You can manually switch to using the XmlSerializer by applying [XmlSerializerFormat] attribute attribute to your service:
 

[ServiceContract]
[XmlSerializerFormat]
public class BankingService
{
    public void ProcessTransaction(BankingTransaction bt)
    {
        // Code not shown.

    }
}

// BankingTransaction is not a data contract class, but is an XmlSerializer-compatible class instead.
public class BankingTransaction
{
    [XmlAttribute]
    public string Operation;

    [XmlElement]
    public Account fromAccount;     // Account class must also be XmlSerializer-compatible.

    [XmlElement]
    public Account toAccount;

    [XmlElement]
    public int amount;
}

More topics to refer to in MSDN

Message Contracts

When building typical WCF application, you need to pay close attention to data structures and serialization issues and do not need to worry about the messages in which the data is carried. However, there are cases in which you need to have complete control over the structure of the generated SOAP message. This is especially true when interoperability is very important or to specifically control security issues at the level of the message or message part. In these cases, you can create a message contract that enables you to use a type for a parameter or return value that serializes directly into the precise SOAP message that you need.

Recall that WCF supports operations modelled on either the Remote Procedure Call (RPC) style or the messaging style. An RPC-style operation is an operation in which any serializable type can be used and the features available to you are those features available to local calls, such as multiple parameters, ref and out parameters, and so on. In this style, the structure of the data in the underlying messages is controlled by the form of serialization chosen, but the messages themselves are created by the WCF runtime to support the operation. The following code example shows a service operation modelled on the RPC style.

[OperationContract]
public BankingTransactionResponse PostBankingTransaction(BankingTransaction bt);

Normally, a data contract is sufficient to define the schema for the messages. For instance, in the preceding example, it is sufficient for most applications if BankingTransaction and BankingTransactionResponse have data contracts to define the contents of the underlying SOAP messages. However, sometimes it is necessary to precisely control how a type gets mapped to a SOAP message transmitted over the wire. The most common scenario for this is inserting custom SOAP headers. Another common scenario is to define security properties for the message's headers and body. Messaging-style operations enable this control.

A messaging-style operation is an operation in which there is at most one parameter and at most one return value where both types are message types; that is, they serialize directly into a specified SOAP message structure. This may be any type marked with the [MessageContract] attribute or the Message type. The following code example shows an operation similar to the preceding one with the difference that it uses the messaging style:

[ServiceContract(Namespace="www.diranich.com/wcf/samples")]
public interface ISomeInterface
{
    // Valid
    [OperationContract]
    BankingTransactionResponse Process(BankingTransaction bt);

    // Valid
    [OperationContract]
    void Store(BankingTransaction bt);

    // Valid
    [OperationContract]
    BankingTransactionResponse GetResponse();

    //Invalid - the return type is not a message contract.
    [OperationContract]
    bool Validate(BankingTransaction bt);

    //Invalid. There is more than one parameter.
    [OperationContract]
    void Reconcile(BankingTransaction bt1, BankingTransaction bt2);
}

Defining Message Contracts

Defining message contracts is very easy:

Note that the [MessageHeade] attribute and [MessageBodyMember] attribute can be applied to all fields, properties and events, regardless of whether they are public, private, protected or internal. For example:

[MessageContract]
public class BankingTransaction
{
    [MessageHeader] public Operation operation;
    [MessageHeader] public DateTime transactionDate;
    [MessageBodyMember] private Account sourceAccount;
    [MessageBodyMember] private Account targetAccount;
    [MessageBodyMember] public int amount;
}

Using Arrays Inside Message Contracts

There are two ways to use arrays of repeating elements in message contracts:

[MessageContract]
public class BankingDepositLog
{
    [MessageHeader] public int numRecords
    [MessageHeader] public DepositRecord records[];
    [MessageHeader] public int branchID;
}

This results in SOAP headers similar to the following:

<BankingDepositLog>
<numRecords>3</numRecords>
    <records>
        <DepositRecord>Record1</DepositRecord>
        <DepositRecord>Record2</DepositRecord>
        <DepositRecord>Record3</DepositRecord>
    </records>
    <branchID>20643</branchID>
</BankingDepositLog>

<numRecords>3</numRecords>
    <records>Record1</records>
    <records>Record2</records>
    <records>Record3</records>
<branchID>20643</branchID>

Soap Header Attributes

The SOAP standard defines the following attributes that may exist on a header:

WCF does not perform any processing of these attributes on incoming messages, except for the MustUnderstand attribute: WCF does not object to the missing headers or to extra headers that are not expected. The one exception to this rule is if the extra header has a MustUnderstand attribute set to true in the incoming SOAP message – in this case, an exception is thrown because a header that must be understood cannot be processed. Message bodies have similar versioning rules - both missing and additional message body parts are ignored.

When sending a message, these attributes are not emitted by default. This can be changed in two ways as shown in the following examples:

// Approach 1
MessageContract]
public class BankingTransaction
{
    [MessageHeader(Actor="http://auditingservice.contosobank.com", MustUnderstand=true)] public bool IsAudited;
    [MessageHeader] public Operation operation;
    [MessageBodyMember] public BankingTransactionData theData;
}

// Approach 2 (see code associated code below)
[MessageContract]
public class BankingTransaction
{
    [MessageHeader] public MessageHeader<bool> IsAudited;
    [MessageHeader] public Operation operation;
    [MessageBodyMember] public BankingTransactionData theData;
}
 

// application code for approach 2
BankingTransaction bt = new BankingTransaction();
bt.IsAudited = new MessageHeader<bool>();
bt.IsAudited.Content = false;                 //Set IsAudited header value to "false"
bt.IsAudited.Actor="http://www.diranieh.com";
bt.IsAudited.MustUnderstand=true;

Message Class

All communication between clients and services ultimately results in Message instances being sent and received. Usually, you would not interact with the Message class directly. Instead, WCF service model constructs, such as data contracts, message contracts, and operation contracts, would be used to describe incoming and outgoing messages.  However, in some advanced scenarios you can program using the Message class directly. For example:

  1. You need an alternative way of creating outgoing message contents (for example, creating a message directly from a file on disk) instead of serializing .NET Framework objects.

  2. You need an alternative way of using incoming message contents (for example, when you want to apply an XSLT transform to the raw XML contents) instead of deserializing into .NET Framework objects.

  3. You need to deal with messages in a general way regardless of message contents (for example, when routing or forwarding messages when building a router, load-balancer, or a pub-sub system).

Message is a general-purpose container for data, but its design closely follows the design of a message in the SOAP protocol. Just like in SOAP, a Message has both a message body and headers. The message body contains the actual payload data, while the headers contain additional named data containers. The rules for reading and writing the body and the headers are different: for example, the headers are always buffered in memory and may be accessed in any order any number of times, while the body may be read only once and may be streamed. Normally, when using SOAP, the Message body is mapped to the SOAP body and the Message headers are mapped to the SOAP headers.

Note that if Message is used anywhere in an operation, the following restrictions apply:

  1. The operation cannot have any out or ref parameters.

  2. There cannot be more than one input parameter. If the parameter is present, it must be either Message or a message contract type.

  3. The return type must be either void, Message, or a message contract type.

For example:

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    Message GetData();

    [OperationContract]
    void PutData(Message m);
    }

Creating Message instances

The Message class provides static CreateMessage factory methods that can be used to create simple messages. For example, to return an object in a message:

[DataContract]
public class Person
{
    [DataMember] public string name;
    [DataMember] public int age;
}

[ServiceContract]
public interface IMyService
{
    [OperationContract]
    Message GetData();

    [OperationContract]
    Message GetData2();

    [OperationContract]
    Message GetData3();

    [OperationContract]
    void PutData(Message m);
}

public class MyService1 : IMyService
{
    // Return an object in a message. This creates a message whose body is the serialized
    // representation of the given object (by default DataContractSerializer is used)

    public Message GetData()
    {
        // Create a object that will contain relevant data
        Person p = new Person();
        p.name = "John Doe";
        p.age = 42;

        // Construct message object
        MessageVersion ver = OperationContext.Current.IncomingMessageVersion;
        return Message.CreateMessage(ver, "GetDataResponse", p);
    }

    // Pass an XmlReader to the message. In this case, the body of the message will contain the
    // XML resulting from reading from the passed XML reader
    public Message GetData2()   
    {
        FileStream stream = new FileStream("myfile.xml",FileMode.Open);
        XmlDictionaryReader xdr = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas());
        MessageVersion ver = OperationContext.Current.IncomingMessageVersion;
        return Message.CreateMessage(ver,"GetDataResponse",xdr);
    }

    // The following shows how to create fault messages
    public Message GetData3()
    {
        FaultCode fc = new FaultCode("Receiver");
        MessageVersion ver = OperationContext.Current.IncomingMessageVersion;
        return Message.CreateMessage(ver,fc,"Bad data","GetDataResponse");
    }

    public void PutData(Message m)
    {
       // Not implemented.
    }
}

Example

The following example illustrates how to use data and message contracts to manage customer information. Project and file layout is illustrated in the following solution explorer:

The sections below contains code for all projects and is arranged as follows:

Data Contracts

Recall the following basic points about data contracts:

And recall the following about the [KnownType] attribute:

// Address is a DataContract that is used with CustomerMessage messsage contract
//
// Recall that all class members of a message contract (i.e., CustomerMessage)
// annotated with [MessageHeader] or [MessageBodyMember] must be serializable.
// The data conrtact for Address ensures that it is serializable so that it can
// be used in CustomerMessage
[DataContract(Namespace = "http://www.diranieh.com/wcf/samples")]
public class Address
{
    // Data members
    private string _AddressLine1 = string.Empty;
    private string _AddressLine2 = string.Empty;
    private string _City = string.Empty;
    private string _PostCode = string.Empty;

    // Constructors
    public Address(string l1, string l2, string city, string code)
    {
        _AddressLine1 = l1;
        _AddressLine2 = l2;
        city = _City;
        _PostCode = code;
    }

    // Properties
    [DataMember]
    public string AddressLine1
    {
        get { return _AddressLine1;}
        set { _AddressLine1 = value;}
    }

    [DataMember]
    public string AddressLine2
    {
        get { return _AddressLine2; }
        set { _AddressLine2 = value; }
    }

    [DataMember]
    public string City
    {
        get { return _City; }
        set { _City = value; }
    }

    [DataMember]
    public string PostCode
    {
        get { return _PostCode; }
        set { _PostCode = value; }
    }

    public override string ToString()
    {
        StringBuilder sb = new StringBuilder();
        sb.AppendFormat("{0}{1}", AddressLine1, Environment.NewLine);
        sb.AppendFormat("{0}{1}", AddressLine2, Environment.NewLine);
        sb.AppendFormat("{0} {1}{2}", City, PostCode, Environment.NewLine);
        return sb.ToString();
    }
}

// See class Customer2 for information on the [KnownType] attribute
[DataContract(Namespace="http://www.diranieh.com/wcf/samples")]
[KnownType(typeof(Customer2))]
public class Customer
{
    // Fields
   
private string _strName;      // [DataMember] not applied, hence, not serialized

    [DataMember]                 // [DataMember] applied, hence, serialized. Note that
   
private string _strAddress;   // member accessibility does not affect data contract

    // Properties
   
[DataMember]
   
public string Name            // [DataMember] applied, hence, serialized
    {
        get { return _strName; }
        set { _strName = value; }
    }

    [DataMember]
   
public string Address          // [DataMember] applied, hence, serialized
   
{
        get { return _strAddress; }
        set { _strAddress = value; }
    }
}

// The Customer2 data contract is used in the ICustomerService service contract
// to illustrate the basics of data contracts.
//
// Customer2 derives from Customer. With the use of [KnownType] attribute (on class
// Customer), Customer2 can be used in place of Customer in the service implementation.
[DataContract(Namespace = "http://www.diranieh.com/wcf/samples")]
public class Customer2 : Customer
{
    // Fields
   
private DateTime _dtDOB;

    // Properties
   
[DataMember]
    public DateTime DOB
    {
        get { return _dtDOB; }
        set { _dtDOB = value; }
    }
}

Message Contracts

Recall the following points about message contracts:

// CustomerMessage represents a custom user-defined messaged that will be passed to
// and from service operations. Instead of individual service operations to manager
// the customer, the custom message contains the operation that should be applied.
[MessageContract]
public class CustomerMessage
{
    private Guid _id;
    private Address _address;
    private string _operation;
    private string _status;

    [MessageHeader]
    public string Operation
    {
        get { return _operation; }
        set { _operation = value; }
    }

    [MessageBodyMember]
    public string Status
    {
        get { return _status; }
        set { _status = value; }
    }

    [MessageBodyMember]
    public Guid CustomerID
        {
        get { return _id; }
        set { _id = value; }
    }

    // Because Address is a user-defined type, it has a data contract so that it can
    // be serialized
    [MessageBodyMember]
    public Address CustomerAddress
    {
        get { return _address; }
        set { _address = value; }
    }
}

Service Contracts

There are three services:

[ServiceContract(Namespace="http://www.diranieh.com/wcf/samples")]
public interface ICustomerService
{
    // No DataContract required since return type is a primitive type
    [OperationContract]
    int GetCustomerCount();

    // DataContract is required since Customer is a user-defined type
    [OperationContract]
    int AddCustomer(Customer customer);

    // DataContract is required since Customer is a user-defined type
    [OperationContract]
    Customer GetCustomer(int id);
}

/* The following class implements the ICustomerService contract. It is assumed
that the class manages customers by interacting with a database */
[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall)]
public class CustomerService : ICustomerService
{
    // Having a constructor allows you to monitor when objects are created. For example,
    // with InstanceContextMode.PerCall, an instance will be created for each call.
    // This is usually a good place to debug and monitor incoming calls.
    // Becuase CustomerService is configured to be PerCall, the constructor will
    // be invoked for each incoming call
    public CustomerService()
    {
        OperationContext opContext = OperationContext.Current;

        // Output some debugging info
        Trace.WriteLine("opContext.Channel.OperationTimeout: " + opContext.Channel.OperationTimeout);
        Trace.WriteLine("opContext.Channel.LocalAddress.Uri: " + opContext.Channel.LocalAddress.Uri);
        Trace.WriteLine("opContext.Channel.SessionId: " + opContext.Channel.SessionId);
        Trace.WriteLine("opContext.Host.Description: " + opContext.Host.Description);
        Trace.WriteLine("opContext.IncomingMessageHeaders: " + opContext.IncomingMessageHeaders);

        // Display incoming message headers
        foreach (MessageHeaderInfo mhi in opContext.IncomingMessageHeaders)
            Trace.WriteLine("Message Header: " + mhi.ToString() );
    }

    #region ICustomerService Members
    public int GetCustomerCount()
    {
        Trace.WriteLine("GetCustomerCount");

        // Get customer count from database
        return GetCustomerCountFromDB();
    }

    public int AddCustomer(Customer customer)
    {
        Trace.WriteLine("AddCustomer: " + customer.Name);

        // Add customer to database
        return AddCustomerToDB(customer);
    }

    // Note that this method GetCustomerFromDB return Customer2 but it returned
    // as Customer. Customer2 class is listed as a known type for Customer class
    public Customer GetCustomer(int id)
    {
        Trace.WriteLine("GetCustomer: " + id);

        // Get customer from database
        return GetCustomerFromDB(id);
    }
    #endregion

    #region Helpers - dummy implementations
    private int AddCustomerToDB(Customer customer)
    {
        Random r = new Random(10);
        return r.Next(0, 100);
    }

    private Customer2 GetCustomerFromDB(int id)
    {
        Customer2 cust = new Customer2();
        cust.Name = "Yazan";
        cust.Address = "DotNetVille";
        cust.DOB = DateTime.Parse("01/01/1999");
        return cust;
    }

    private int GetCustomerCountFromDB()
    {
        return 10;
    }
    #endregion
}

/* Output when calling GetCustomerCount:

opContext.Channel.OperationTimeout: 00:03:00
opContext.Channel.LocalAddress.Uri: net.tcp://localhost:9000/SampleServiceContracts
opContext.Channel.SessionId: uuid:25d45439-aeac-4e7f-b2af-37bb8577758e;id=1
opContext.Host.Description: System.ServiceModel.Description.ServiceDescription
opContext.IncomingMessageHeaders: System.ServiceModel.Channels.MessageHeaders
Message Header: <Action d1p1:mustUnderstand="1" xmlns:d1p1="http://www.w3.org/2003/05/soap-envelope" xmlns="http://www.w3.org/2005/08/addressing">http://www.diranieh.com/wcf/samples/ICustomerService/GetCustomerCount</Action>
Message Header: <MessageID xmlns="http://www.w3.org/2005/08/addressing">urn:uuid:6898282e-e37c-484a-9494-322db9015124</MessageID>
Message Header: <ReplyTo xmlns="http://www.w3.org/2005/08/addressing">
<Address>http://www.w3.org/2005/08/addressing/anonymous</Address>
</ReplyTo>
Message Header: <To d1p1:mustUnderstand="1" xmlns:d1p1="http://www.w3.org/2003/05/soap-envelope" xmlns="http://www.w3.org/2005/08/addressing">net.tcp://localhost:9000/SampleServiceContracts</To>

Output when calling AddCustomer:

opContext.Channel.OperationTimeout: 00:03:00
opContext.Channel.LocalAddress.Uri: net.tcp://localhost:9000/SampleServiceContracts
opContext.Channel.SessionId: uuid:25d45439-aeac-4e7f-b2af-37bb8577758e;id=1
opContext.Host.Description: System.ServiceModel.Description.ServiceDescription
opContext.IncomingMessageHeaders: System.ServiceModel.Channels.MessageHeaders
Message Header: <Action d1p1:mustUnderstand="1" xmlns:d1p1="http://www.w3.org/2003/05/soap-envelope" xmlns="http://www.w3.org/2005/08/addressing">http://www.diranieh.com/wcf/samples/ICustomerService/AddCustomer</Action>
Message Header: <MessageID xmlns="http://www.w3.org/2005/08/addressing">urn:uuid:f28e9347-d79e-4d67-8e31-b64de0ee38bf</MessageID>
Message Header: <ReplyTo xmlns="http://www.w3.org/2005/08/addressing">
<Address>http://www.w3.org/2005/08/addressing/anonymous</Address>
</ReplyTo>
Message Header: <To d1p1:mustUnderstand="1" xmlns:d1p1="http://www.w3.org/2003/05/soap-envelope" xmlns="http://www.w3.org/2005/08/addressing">net.tcp://localhost:9000/SampleServiceContracts</To>

Note that both calls have the same session id. If client were to close the proxy between each call,
there would be different sessions ids for each call.
*/

[ServiceContract(Namespace = "http://www.diranieh.com/wcf/samples")]
public interface ICustomerMessageService
{
    // Note: The following error is generated if method's signature was not valid
    // (return type is not a message conrtract, more than on parameter):
    // "
The operation 'ProcessCustomer' could not be loaded because it has a parameter or return
    // type of type System.ServiceModel.Channels.Message or a type that has
    // MessageContractAttribute and other parameters of different types. When using
    // System.ServiceModel.Channels.Message or types with MessageContractAttribute,
    // the method must not use any other types of parameters
"
    [OperationContract]
    CustomerMessage ProcessCustomer(CustomerMessage customer);
}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class CustomerMessageService : ICustomerMessageService
{
    #region Constructors
    public CustomerMessageService()
    {
        OperationContext opContext = OperationContext.Current;

        // Output some debugging info
        Trace.WriteLine("opContext.Channel.OperationTimeout: " + opContext.Channel.OperationTimeout);
        Trace.WriteLine("opContext.Channel.LocalAddress.Uri: " + opContext.Channel.LocalAddress.Uri);
        Trace.WriteLine("opContext.Channel.SessionId: " + opContext.Channel.SessionId);
        Trace.WriteLine("opContext.Host.Description: " + opContext.Host.Description);
        Trace.WriteLine("opContext.IncomingMessageHeaders: " + opContext.IncomingMessageHeaders);

        // Display incoming message headers
        foreach (MessageHeaderInfo mhi in opContext.IncomingMessageHeaders)
            Trace.WriteLine("Message Header: " + mhi.ToString() );
    }
    #endregion

    #region ICustomerMessageService Members
    public CustomerMessage ProcessCustomer(CustomerMessage customer)
    {
        // Debugging
        Trace.WriteLine("Customer ID: " + customer.CustomerID + "\nAddress: " + customer.CustomerAddress.ToString());

        // Process the customer based on the supplied operation
        switch (customer.Operation)
        {
            case "Delete":
                // Delete customer ...
                customer.Status = "Deleted";
                break;
            case "Add":
                // Add customer ...
                customer.Status = "Added";
                break;
            default:
                break;
        }
        return customer;
    }
    #endregion
}

/* Note the following constructor output when this service is called:

opContext.Channel.OperationTimeout: 00:03:00
opContext.Channel.LocalAddress.Uri: net.tcp://localhost:9001/SampleServiceContracts
opContext.Channel.SessionId: uuid:9236a01c-414f-41d3-948f-ce5bf3b6a42f;id=1
opContext.Host.Description: System.ServiceModel.Description.ServiceDescription
opContext.IncomingMessageHeaders: System.ServiceModel.Channels.MessageHeaders
Message Header: <Action d1p1:mustUnderstand="1" xmlns:d1p1="http://www.w3.org/2003/05/soap-envelope" xmlns="http://www.w3.org/2005/08/addressing">http://www.diranieh.com/wcf/samples/ICustomerMessageService/ProcessCustomer</Action>
Message Header: System.ServiceModel.Channels.HeaderInfoCache+HeaderInfo
Message Header: <MessageID xmlns="http://www.w3.org/2005/08/addressing">urn:uuid:2f31ce1d-3844-4005-b831-10c86a3187fc</MessageID>
Message Header: <ReplyTo xmlns="http://www.w3.org/2005/08/addressing">
<Address>http://www.w3.org/2005/08/addressing/anonymous</Address>
</ReplyTo>
Message Header: <To d1p1:mustUnderstand="1" xmlns:d1p1="http://www.w3.org/2003/05/soap-envelope" xmlns="http://www.w3.org/2005/08/addressing">net.tcp://localhost:9001/SampleServiceContracts</To>
*/

[ServiceContract(Namespace = "http://www.diranieh.com/wcf/samples")]
public interface ICustomerUntypedMessageService
{
    // It is important to specify the Action and Reply action values to ensure that they are
    // the same between the client and the service. If Action and ReplyAction properties below
    // were not specified, the following error would be generated:
    // "Outgoing request message for operation 'ProcessCustomer' specified
    // Action='http://www.diranieh.com/wcf/samples/RequestAction', but contract for that operation
    // specifies Action='
http://www.diranieh.com/wcf/samples/ICustomerUntypedMessageService/ProcessCustomer'.
    // The Action specified in the Message must match the Action in the contract, or the operation
    // contract must specify Action='*' ".
   
[OperationContract(Action = CustomerUntypedMessageService.Request, ReplyAction = CustomerUntypedMessageService.Reply)]
   
Message ProcessCustomer(Message msg);
}

public class CustomerUntypedMessageService : ICustomerUntypedMessageService
{
    // Data members
    public const String Reply = "http://www.diranieh.com/wcf/samples/ReplyAction";
    public const String Request= "http://www.diranieh.com/wcf/samples/RequestAction";

    #region ICustomerUntypedMessageService Members
    public Message ProcessCustomer(Message msg)
    {
        // Get message data
        string[] data = msg.GetBody<string[]>();

        // Process message based on message data
        string operation = data[0];
        string param1 = data[1];
        string param2 = data[2];

        object oResult = null;
        if (operation == "Add")
            oResult = Convert.ToInt32(param1) + Convert.ToInt32(param2);
        if (operation == "Substract")
            oResult = Convert.ToInt32(param1) - Convert.ToInt32(param2);
        if (operation == "Multiply")
            oResult = Convert.ToInt32(param1) * Convert.ToInt32(param2);

        // Return response
        Message reply = Message.CreateMessage(msg.Version, Reply, oResult);
        return reply;
    }
    #endregion
}

Host

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <!-- To create this file, either create it manually inside VS.NET making use of intellisense,
    or use the SvcConfigEditor.exe utility to create and edit WCF configuration files -->
    <system.serviceModel>
        <services>

            <!-- SERVICE 1 -->
            <!-- Recall that name corresponds to the fully-qualified name of the type
            implementing the contract-->
            <service name="ServiceContracts.CustomerService" behaviorConfiguration="GeneralBehavior">

                <!-- Note address syntax for the netTcpBinding binding. Note that 'netTcpBinding'
                is case-sensitive. If binding was equal to 'NetTcpBinding', you would get the following
                error: Configuration binding extension 'system.serviceModel/bindings/NetTcpBinding' could
                not be found. Verify that this binding extension is properly registered in
                system.serviceModel/extensions/bindingExtensions and that it is spelled correctly.-->
                <endpoint   address="net.tcp://localhost:9000/SampleServiceContracts"
                            binding="netTcpBinding"
                            contract="ServiceContracts.ICustomerService"
                            bindingConfiguration="TcpBinding">
                </endpoint>
            </service>

            <!-- SERVICE 2-->
            <!-- Note that the address must specify a different port number or end with a different
            name. Otheriwse, the following error is generated: "A registration already exists for
            URI' net.tcp://localhost:9000/SampleServiceContracts'" -->

            <service name="ServiceContracts.CustomerMessageService" behaviorConfiguration="GeneralBehavior">
                <endpoint address="net.tcp://localhost:9001/SampleServiceContracts"
                          binding="netTcpBinding"
                          contract="ServiceContracts.ICustomerMessageService"
                          bindingConfiguration="TcpBinding">
                </endpoint>
            </service>

            <!-- SERVICE 3-->
            <service name="ServiceContracts.CustomerUntypedMessageService" behaviorConfiguration="GeneralBehavior">
                <endpoint address="net.tcp://localhost:9002/SampleServiceContracts"
                          binding="netTcpBinding"
                          contract="ServiceContracts.ICustomerUntypedMessageService"
                          bindingConfiguration="TcpBinding">
                </endpoint>
            </service>
        </services>

        <!-- Behaviours for the service-->
        <behaviors>
            <serviceBehaviors>
                <!-- The name attribute is used to link the following behaviour to a service via
                the behaviorConfiguration attribute. See the service named 'ServiceContracts.CustomerService' -->
 
               <behavior name="GeneralBehavior">
                    <serviceDebug includeExceptionDetailInFaults="True"/>
                </behavior>
            </serviceBehaviors>
        </behaviors>

        <!-- The following binding is used to configure the 'netTcpBinding' used in the endpoint
        element above. The 'netTcpBinding' is configured to increase timeout values (default was
        1 minute) to allow server-side debugging without letting the client timeout. Otherwise,
        you get the following exception: "This request operation sent to
        net.tcp://localhost:9000/SampleServiceContracts did not receive a reply within the configured
        timeout (00:01:00). The time allotted to this operation may have been a portion of a longer
        timeout. This may be because the service is still processing the operation or because the
        service was unable to send a reply message. Please consider increasing the operation timeout
        (by casting the channel/proxy to IContextChannel and setting the OperationTimeout property)
        and ensure that the service is able to connect to the client -->
        <bindings>
            <netTcpBinding>
                <binding name="TcpBinding"
                         closeTimeout="00:03:00"
                         openTimeout="00:03:00"
                         receiveTimeout="00:03:00"
                         sendTimeout="00:03:00">
               </binding>
            </netTcpBinding>
        </bindings>
    </system.serviceModel>
</configuration>

static class Service_Common
{
    internal static ServiceHost StartService(Type typService)
    {
        ServiceHost sh = new ServiceHost(typService);

        // Open myServiceHost
        sh.Open();

        // Output some debugging information
        Diagnostics.DisplayInfo(sh);

        return sh;
    }

    internal static void StopService(ServiceHost sh)
    {
        // Call StopService from your shutdown logic (i.e. dispose method)
        Trace.WriteLine(" Service state: " + sh.State.ToString());

        if (sh.State != CommunicationState.Closed)
        {
            sh.Close();
            Trace.WriteLine(" Service closed!");
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        // Start all services exposed by this host
        ServiceHost shDC = Service_Common.StartService(typeof(CustomerService));
        ServiceHost shMC = Service_Common.StartService(typeof(CustomerMessageService));
        ServiceHost shUMC = Service_Common.StartService(typeof(CustomerUntypedMessageService));

        // The service can now be accessed until user presses <return> to terminate the service
        Console.WriteLine("The service is ready");
        Console.WriteLine("Press <RETURN> to terminate the service");
        Console.ReadLine();

        // Stop all services exposed by this host
        Service_Common.StopService(shDC);
        Service_Common.StopService(shMC);
        Service_Common.StopService(shUMC);
    }
}

Client

The code below shows the client configuration file and the actual client code:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.serviceModel>
        <client>
            <!-- Again, not the use of a different port number for each each point. See
                 comments in the service'e config file -->

            <endpoint name="BasicDataContractConfig"
                      address="net.tcp://localhost:9000/SampleServiceContracts"
                      binding="netTcpBinding"
                      contract="ICustomerService" >
            </endpoint>

            <endpoint name="BasicMessageContractConfig"
                      address="net.tcp://localhost:9001/SampleServiceContracts"
                      binding="netTcpBinding"
                      contract="ICustomerMessageService" >
            </endpoint>

            <endpoint name="BasicUntypedMessageContractConfig"
                      address="net.tcp://localhost:9002/SampleServiceContracts"
                      binding="netTcpBinding"
                      contract="ICustomerUntypedMessageService" >
            </endpoint>
        </client>
    </system.serviceModel>
</configuration>

// Code for actual client that is used to invoke WCF service
public partial class Window1 : System.Windows.Window
{
    ...

    #region Event handlers
    private void DataContract_ClickHandler(object sender, RoutedEventArgs args)
    {
        CustomerServiceClient proxy = new CustomerServiceClient("BasicDataContractConfig");
        int nCount = proxy.GetCustomerCount();

        // Add a new customer.
        // Note: Initially, I forgot to apply the [DataMember] attribute to the Address property
        // on Customer. Address property was therefore not available on customer instance. After
        // adding [DataMember] to the Customer class, the Address property was available to be
        // invoked on the customer object
        Customer c1 = new Customer();
        c1.Name = "MyName";
        c1.Address = "MyAddress";
        int nNewCustomer = proxy.AddCustomer(c1);

        // Retrieve a customer
        Customer c2 = proxy.GetCustomer( 1234);
        if (c2 is Customer2)
            Trace.WriteLine("DOB: " + ((Customer2)c2).DOB);
    }

    private void MessageContract_ClickHandler(object sender, RoutedEventArgs args)
    {
        // Craeat proxy to service object
        CustomerMessageServiceClient proxy = new CustomerMessageServiceClient("BasicMessageContractConfig");

        // Construct parameter for the AddCustomer method
        // Note: Could not initialize Address via its constructor. Why is constructor not
        // available?
        Address address = new Address();
        address.AddressLine1 = "Line1";
        address.AddressLine2 = "Line2";
        address.City = "MyCity";
        address.PostCode = "MyPostcode";
        CustomerMessage cm = new CustomerMessage();
        cm.CustomerAddress = address;
        cm.Operation = "Add";

        // Call the service object. Note how the proxy is casted to ICustomerMessageService
        // Otherwise, the signature for AddCustomer on the proxy object will be :
        // public void AddCustomer(ref Guid CustomerID, ref Address CustomerAddress);
        // Casting makes the syntax cleaner
       
CustomerMessage cmUpdated = ((ICustomerMessageService)proxy).ProcessCustomer( cm );
        Trace.WriteLine("Customer Status:" + cmUpdated.Status);
    }

    private void UntypedMessageContract_ClickHandler(object sender, RoutedEventArgs args)
    {
        // Create proxy
        CustomerUntypedMessageServiceClient proxy = new CustomerUntypedMessageServiceClient("BasicUntypedMessageContractConfig");

        // Prepare request message
        OperationContextScope scope = new OperationContextScope(proxy.InnerChannel);
        string[] astrMsgData = new string[] {"Add", "1", "2"};
        String Request= "http://www.diranieh.com/wcf/samples/RequestAction";
        Message request = Message.CreateMessage(OperationContext.Current.OutgoingMessageHeaders.MessageVersion,
                                                Request,
                                                astrMsgData);

        // Send message to service
        Message response = proxy.ProcessCustomer(request);

        // Process response
        int nValue= response.GetBody<int>();
        Trace.WriteLine("Value = " + nValue.ToString());

        proxy.Close();
    }
    #endregion
}

Large Data and Streaming

Basic Considerations

WCF is an XML-based communication infrastructure. A major concern for system architects is the footprint of messages sent across the wire. The following sections address this concern.

Text Vs. Binary

While XML-text encoded messages are very transparent and "human readable", binary messages are often fairly obscure in comparison and difficult to decode without tools. This simple difference in legibility easily leads one to neglect that binary messages also often carry inline metadata alongside the payload, which adds overhead just as with XML text messages. This is specifically true for binary formats that aim to provide loose-coupling and dynamic invocation capabilities. In general, the size of a single serialized payload object will be quite similar when comparing text to binary representations.

Still, for certain data types such as numbers, there is a disadvantage to using fixed-size, binary numerical representations such as a 128-bit decimal type instead of plain text as the plain text representation might indeed be several bytes smaller. Text data also might have size benefits from the typically more flexible XML text encoding choices, while some binary formats might choose to default to 16-bit or even 32-bit Unicode (which does not apply to the .NET Binary XML Format).

As a result, the text vs. binary choice is not quite as simple as to assume that binary messages are always smaller than XML-text messages.

Binary Content

One area where binary encodings are clearly superior to text based encodings in terms of the resulting message size are large binary data items such as pictures, videos, sound-clips, or any other form of opaque, binary data that needs to be exchanged between services and their consumers. To fit these types of data into XML text, the common approach is to encode them using the Base64 encoding.

Large Content

By default, WCF processes messages in "buffered mode". This means that the entire content of a message will be present in memory before it is being sent or after it has been received. While that is a good strategy for most scenarios, very large messages might easily end up exhausting a system's resources.

The strategy to deal with very large payloads is streaming. When data is transferred in a streaming mode instead of a buffered mode, the message body contents will be made available by the sender and to the recipient in form of a stream and the message infrastructure continuously forwards the data from sender to receiver as it being made available. The most common scenario in which such large data content transfers occur are transfers of binary data objects that :

  1. Cannot be broken up into a message sequence easily.

  2. Need to be delivered in a very timely manner

  3. Are not available in their entirety when the transfer is initiated.

For data that does not have any of these constraints, it is typically better to send sequences of messages within the scope of a session than one large message. Streaming is available and easily enabled on all standard bindings except the MSMQ-based NetMsmqBinding and the MsmqIntegrationBinding. The MSMQ transport only supports buffered data transfers with a constrained message size, while all other transports do not have any practical message size limit for the vast majority of scenarios.

Streaming can be enabled in three different ways:

Streaming is disabled by setting the transfer mode to Buffered, which is the default setting on all bindings. You can set the transfer mode in configuration as shown in the snippets shown below:

<system.serviceModel>
    <bindings>
        <wsHttpBinding>
            <binding name="ExampleBinding" transferMode="Streaming"/>
        </wsHttpBinding>
    </bindings>
<system.serviceModel>

Programming Model for Streamed Transfers

For receiving streamed data, you need to specify an operation contract that has a single Stream typed input parameter. For returning streamed data you return a Stream reference:

[ServiceContract(Namespace="http://Microsoft.ServiceModel.Samples")]
public interface IStreamedService
{
    [OperationContract]
    Stream Echo(Stream data);

    [OperationContract]
    Stream RequestInfo(string query);

    [OperationContract(OneWay=true)]
    void ProvideInfo(Stream data);
}

Note that adding a second parameter to the Echo() or ProvideInfo() operations below would cause the service model to revert back to a buffered strategy and use the runtime serialization representation of the stream. Only operations with a single input stream parameter are compatible with end-to-end request streaming.