Designing Windows Communication Foundation service contracts

Michele Leroux Bustamante describes the different types of contracts used with WCF services inlcuding ServiceContracts and DataContracts. She also shows the two approaches to developing contracts, Code-First and Contract-First.

This article describes different contracts used with Windows Communication services. It is based on the November 2005 CTP for WinFX.

Messaging is an essential element of distributed systems. For components and services to communicate across process and machine boundaries, the messaging between those components and services must be compatible or at least understood by their respective runtime environments. Messaging is usually compatible in one of these three ways: if communicating platforms are compatible; if there is an adapter that understands both platforms well enough to convert messages; or, if messages conform to a standard supported by both platforms.

Considering the last statement, years ago XML made it possible to describe messages with schema, and shortly afterward Web services introduced a way to generate platform-neutral messaging with Simple Object Access Protocol (SOAP), with a programmatically consumable contract described in Web Services Description Language (WSDL). Since then, advanced web services standards have continued to evolve supplying XML conformance specifications for things like security, attachments and reliability through specifications like OASIS Web Services Security (WSS), WS-Trust, WS-SecureConversation, SAML, MTOM, and WS-ReliableMessaging.

Windows Communication Foundation (WCF, formerly known as Indigo) is built upon the foundation of Web services messaging and related standards, while at the same time making it possible to serialize messages in a more compact binary format, or in a more proprietary way. Still, the core message can always be represented in XML, therefore be considered compatible with any platform that understands XML, and agrees on the contract that defines said messaging between systems.

That’s where we get to the point of this particular article. It’s all about the contract. WCF supports three types of contracts to help describe how services and their callers communicate. A data contract describes how a complex type is serialized as part of a message. A message contract describes the entire SOAP message format, including support for custom headers and individual body elements that can be described by data contracts. The service contract describes a particular service endpoint and its supported operations. Each operation implements a particular message exchange pattern (MEP) to comprise and input message (request), and output message (response), or trigger a callback (duplex). Each of these messages is still ultimately described by a schema generated from the data contracts or message contracts associated with the operation. Each of these contracts leads to the overarching service description (WSDL) that becomes the contract for overall messaging requirements for a service and its endpoints.

There are many approaches to building Windows Communication Foundation service contracts, including different approaches to code-first and contract-first design. You get to pick your approach based on what you have to work with, and that’s where the thrust of this article is. How do you decide if code-first is for you? What approaches to contract-first exist? And, what are we still missing?

Code-first – It’s easy, therefore you love it…

The biggest problem with contract-first today is in the tooling. Code-first implies that you get to take existing types and design an API layer (service) to expose operations and types to the outside world. This is comfortable for most developers, because they don’t have to worry about the XML -- they merely let the runtime generate the WSDL contract and schema definitions for them. What is comfortable is quite often also very productive, and we like productivity don’t we? The business sure does. Of course there are many instances where code-first doesn’t cut it, but none of them have to do with interoperability, really. So long as complex types are converted to schemas that don’t leverage advanced XML schema definition (XSD) features (that may not be supported by all XML processing runtimes), messages will be interoperable. The goal, ultimately, is to generate a WSDL contract that describes service operations and type serialization via schema. We can do that using data contracts, using serializable type, or using message contracts combined with either.

1. What? Code-first is interoperable?

Well sure! You generate schema from code, proxies consume schema and generate a representation of the schema for the caller’s platform. Of course the serialized type has to be meaningful to the caller, so a DataSet in .NET though serializable makes a horrible candidate for cross platform interoperability. Aside from this, one of the biggest interoperability issues between platforms has to do with runtime schema support. Schema is a vast standard that has much more capability than many parsers have support for. Thus, interoperability can be an issue whether you begin with schema (contract-first) or when you generate schema from objects (code-first).

2. Service contracts

In Windows Communication Foundation, service contracts describe the operations supported by a service, the message exchange pattern they use, and the format of each message. The service contract is the driver for generating a service description – so a service must implement at least one service contract. To create a service contract you define an interface with related methods representative of a collection of service operations, and then decorate the interface with the ServiceContractAttribute to indicate it is a service contract. Methods in the interface that should be included in the service contract are decorated with the OperationContractAttribute. Listing 1 illustrates the use of these attributes.

*** Listing 1: Example of a Windows Communication Foundation service contract.

[ServiceContract(Name="EventsService", Namespace="http://www.thatindigogirl.com/2005/12/EventsService")]
public interface IEventsService
{
  [OperationContract(Name = "SaveEvent", Action = 
"http://www.thatindigogirl.com/2005/12/EventsService/SaveEvent", ReplyAction = 
 "http://www.thatindigogirl.com/2005/12/EventsService/SaveEventsResponse")]
  void SaveEvent(LinkItem item);
  [OperationContract(Name = "GetEvent", Action = 
"http://www.thatindigogirl.com/2005/12/EventsService/GetEvent", ReplyAction = 
 "http://www.thatindigogirl.com/2005/12/EventsService/GetEventResponse")]
   LinkItem GetEvent();
}

Using these attributes, details like the namespace, action URI, and message exchange pattern semantics (request-reply, one way, or callback) are controlled. In Listing 1 SaveEvent() and GetEvent() operations both follow the request-reply pattern. Explicitly setting the Name, Action and ReplyAction properties for OperationContractAttribute are not necessary, but do give you the ability to control how WSDL is generated rather than accepting the defaults. Likewise defaults are generated for the contract name and namespace however, it is important to supply values for these. By default the namespace is set to “http://tempuri.org” and the contract name uses the interface name, IEventsService. Interface naming conventions for CLR types usually prefix with the letter “I” but this is not customary for WSDL contracts so I recommend controlling the contract name.

The service merely implements this contract (possibly more than one contract) as shown in Listing 2.

*** Listing 2: Example of a service implementation

public class EventsService: IEventsService
{
  private LinkItem m_linkItem;

  public void SaveEvent(LinkItem item)
  {
    m_linkItem = item;
  }
  public LinkItem GetEvent()
  {
    return m_linkItem;
  }
}

Ultimately it is the contract implemented by a service that feeds the service description and the WSDL document’s list of operations and messages. The payload requirements for each message are described by schema in the WSDL, so we still need to discuss how those type schemas are generated. In this example, the data type passed to and returned from each respective operation is a LinkItem type. Types marked with the DataContractAttribute or SerializableAttribute can be included in the contract, making it possible for the service description to be generated. So, the service contract creates a frame for the overall service API, but it relies on data contracts, serializable types, and message contracts to actually generate the schema for each message.

3. Data Contracts

The WCF way to generate an XML schema from a CLR type is with a data contract. Data contracts describe how a CLR type maps to schema with an opt-in approach. The XmlFormatter uses the information provided in a data contract to handle serialization and deserialization. This allows developers to work with familiar objects at runtime, while the WCF runtime hides the XML goo. The XmlFormatter is the successor of the XmlSerializer for WCF services -- with the exception that currently, the XmlSerializer still provides more granular control over object serialization to XML. As such there will still be cases where you will still want to use the XmlSerializer, for now.

4. Creating a Data Contract

First you decorate the type with the DataContractAttribute, then you select members to include in serialization with the DataMemberAttribute. Listing 3 illustrates the LinkItem type mentioned earlier, as a data contract.

*** Listing 3: An example of a data contract

[DataContract(Namespace="http://schemas.thatindigogirl.com/2005/12/LinkItems", Name="LinkItem")]
public class LinkItem
{
  [DataMember(Name="Id", IsRequired=false, Order=0)] 
  private long m_id;
  [DataMember(Name = "Title", IsRequired = true, Order = 1)] 
  private string m_title;
  [DataMember(Name="Description", IsRequired = true, Order = 2)] 
  private string m_description;
  [DataMember(Name="DateStart", IsRequired = true, Order = 3)] 
  private DateTime m_dateStart;
  [DataMember(Name="DateEnd", IsRequired = false, Order = 4)] 
  private Nullable<DateTime> m_dateEnd;
  [DataMember(Name="Url", IsRequired = false, Order = 5)] 
  private string m_url;
  [DataMember(Name="LinkType", IsRequired = false, Order=6)] 
  private string m_linkType;

  public DateTime DateStart
  {
    get { return m_dateStart; }
    set { m_dateStart = value; }
  }

  public string LinkType
  {
    get { return m_linkType; }
    set { m_linkType = value; }
   }

   public DateTime DateEnd
   {
     get { return m_dateEnd; }
     set { m_dateEnd = value; }
   }
       
   public string Url
   {
     get { return m_url; }
     set { m_url = value; }
   }
        
   public long Id
   {
     get { return m_id; }
     set { m_id = value; }
   }

   public string Title
   {
     get { return m_title; }
     set { m_title = value; }
   }

  public string Description
  {
    get { return m_description; }
    set { m_description = value; }
  }
}

Let me explain some of the settings for the DataContractAttribute first. You’ll notice I’m providing a value for the Name and Namespace properties. In fact the name would default to the type name, so in this case it is redundant, but this clarifies the distinction between the CLR type and the schema naming convention, regardless. The namespace is another story. Schemas that describe complex types usually belong to a specific namespace. By default, the namespace provided for you will derive from “http://schemas.datacontract.org/2004/07”. Listing 4 illustrates a default schema for LinkItems without including the Namespace property setting, nor the Required or Order property settings for the DataMemberAttribute.

*** Listing 4: Schema generated for LinkItem without specifying namespace or member order

<?xml version="1.0" encoding="utf-8" ?> 
<xs:schema elementFormDefault="qualified" targetNamespace="http://schemas.datacontract.org/2004/07/EventsService"
 xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:tns="http://schemas.datacontract.org/2004/07/EventsService" 
xmlns:ser="http://schemas.microsoft.com/2003/10/Serialization/">
<xs:complexType name="LinkItem">
<xs:sequence>
  <xs:element minOccurs="0" name="DateEnd" type="xs:dateTime" /> 
  <xs:element minOccurs="0" name="DateStart" type="xs:dateTime" /> 
  <xs:element minOccurs="0" name="Description" nillable="true" type="xs:string" /> 
  <xs:element minOccurs="0" name="Id" type="xs:long" /> 
  <xs:element minOccurs="0" name="Title" nillable="true" type="xs:string" /> 
  <xs:element minOccurs="0" name="Url" nillable="true" type="xs:string" /> 
</xs:sequence>
</xs:complexType>
<xs:element name="LinkItem" nillable="true" type="tns:LinkItem" /> 
</xs:schema>

Only fields or properties marked with the DataMemberAttribute are included in serialization (therefore, in the schema definition). By default, they are serialized in alphabetical order to avoid dependencies on reflection ordering which can change if you modify the class definition. In practice, to achieve an orderly schema you should always use the Order property (shown in Listing 3). In addition, to indicate which members should be required elements (the default is optional) use the Required property (also shown in Listing 3). Listing 5 shows the resulting schema for the data contract shown in Listing 3, using these recommended practices.

*** Listing 5: A better schema generated for LinkItem from the code in Listing 3

<?xml version="1.0" encoding="utf-8" ?> 
<xs:schema elementFormDefault="qualified" targetNamespace=
"http://schemas.thatindigogirl.com/2005/12/LinkItems" xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://schemas.thatindigogirl.com/2005/12/LinkItems"
xmlns:ser="http://schemas.microsoft.com/2003/10/Serialization/">
<xs:complexType name="LinkItem">
<xs:sequence>
  <xs:element minOccurs="0" name="Id" type="xs:long" /> 
  <xs:element name="Title" nillable="true" type="xs:string" /> 
  <xs:element name="Description" nillable="true" type="xs:string" /> 
  <xs:element name="DateStart" type="xs:dateTime" /> 
  <xs:element minOccurs="0" name="DateEnd" type="xs:dateTime" /> 
  <xs:element minOccurs="0" name="Url" nillable="true" type="xs:string" /> 
</xs:sequence>
</xs:complexType>
<xs:element name="LinkItem" nillable="true" type="tns:LinkItem" /> 
</xs:schema>

The use of data contract as I’ve described here results in a WSDL contract that expects the SaveEvent() operation to receive a LinkItem type as described in the schema from Listing 5. The same contract expects the GetEvent() operation to return a LinkItem type. If the message payload passed from a client does not match the schema (including all required elements, for example) the message will be rejected.

5. Limitations designing Windows Communication Foundation services with Data Contract

Currently data contracts with the XmlFormatter do not provide granular control over XML serialization. For example I cannot use a DataMemberAttribute to indicate a DateTime should be serialized as xs:date instead of xs:dateTime. In cases like this use the XmlSerializer to be discussed next.

A bigger limitation with the data contract is that it presumes you can edit those types you want to serialize, to opt-in data members for serialization. The fact is, when you design a service, you are more likely to be working with business objects that already exist. Your middle-tier exposes functionality that interacts with the rest of the system, and your service is merely an entry point to that functionality (see Figure 1).

Even if you have control over those business objects, do you want to litter your middle-tier with attributes related to the service level? I suppose the answer is yes if you are in a hurry, and the type you want to serialize generates the schema you are looking for in a service contract. I’m all for productivity. For example, this allows the Windows Communication Foundation runtime to deserialize incoming messages and create the type for you. But, in the case where you cannot (or decidedly should not) modify the type for serialization, what then? You don’t want is to create a dupe of the type at the service level, and manually map it to a business tier type. That means you create two objects for each inbound message, which increases pressure on the garbage collector and other unnecessary overhead.

To overcome this problem, you probably want to receive the XML stream at the service level, and build the type yourself. I’ll talk about this a little later.

6. Serializable Types

Though data contracts are the recommended serialization construct for Windows Communication Foundation services, the XmlFormatter is also capable of handling serializable types. Types decorated with the SerializableAttribute or that implement ISerializable can then be included in a service contract. In fact, primitive types like int, long, and double; structure like enum; and reference types like string are all serializable types therefore included in the service contract because they have the SerializableAttribute. Any business objects you had the foresight to make serializable can then be useful when designing a service contract that exposes those types.

The XmlSerializer also makes it possible to exercise more granular control over schema generation, making it possible for a complex type to be manipulated to meet the needs of the WSDL contract it generates, yet allow the runtime to deserialize messages and build the runtime instance of the object. Once again, developers get to work with familiar objects as they program services and clients.

7. Serializable Types and the XmlFormatter

You can consume existing serializable types in a service contract. The XmlFormatter will by default serialize all fields regardless of accessibility setting. Consider the LinkItem type marked serializable in Listing 6.

*** Listing 6: A basic serializable type

        [Serializable]
        public class LinkItem
        {
            private long m_id;
            private string m_title;
            private string m_description;
            private DateTime m_dateStart;
            private DateTime? m_dateEnd;
            private string m_url;

            public DateTime DateStart
            {
                get { return m_dateStart; }
                set { m_dateStart = value; }
            }
            public DateTime? DateEnd
            {
                get { return m_dateEnd; }
                set { m_dateEnd = value; }
            }
            public string Url
            {
                get { return m_url; }
                set { m_url = value; }
            }
            public long Id
            {
                get { return m_id; }
                set { m_id = value; }
            }
            public string Title
            {
                get { return m_title; }
                set { m_title = value; }
            }
            public string Description
            {
                get { return m_description; }
                set { m_description = value; }
            }
        }

This generates the schema in Listing 7.

*** Listing 7: Schema generated for a serializable type using the XmlFormatter

<?xml version="1.0" encoding="utf-8" ?> 
<xs:schema elementFormDefault="qualified" 
targetNamespace="http://schemas.datacontract.org/2004/07/EventsService"
xmlns:xs="http://www.w3.org/2001/XMLSchema" 
xmlns:tns="http://schemas.datacontract.org/2004/07/EventsService" 
xmlns:ser="http://schemas.microsoft.com/2003/10/Serialization/">
<xs:complexType name="LinkItem">
<xs:sequence>
  <xs:element name="m_dateEnd" nillable="true" type="xs:dateTime" /> 
  <xs:element name="m_dateStart" type="xs:dateTime" /> 
  <xs:element name="m_description" nillable="true" type="xs:string" /> 
  <xs:element name="m_id" type="xs:long" /> 
  <xs:element name="m_title" nillable="true" type="xs:string" /> 
  <xs:element name="m_url" nillable="true" type="xs:string" /> 
</xs:sequence>
</xs:complexType>
<xs:element name="LinkItem" nillable="true" type="tns:LinkItem" /> 
</xs:schema>

You’ll notice a few things about this schema:

  • The sort order is alphabetic, the XmlFormatter default
  • Element names match the type’s internal field names, rather that the property, since serializable types wrap the wire.

You can still use the resulting schema to generate types for the client to consume, however naming conventions may not be what you’d prefer.

8. Invoking the XmlSerializer

A Windows Communication Foundation service contract can elect to use the XmlSerializer instead of the XmlFormatter, by specifying the XmlSerializerAttribute on the service contract as shown in Listing 8.

*** Listing 8: Selecting the XmlSerializer

[ServiceContract(Name="EventsService", 
Namespace="http://www.thatindigogirl.com/2005/12/EventsService",Session=true)]
[XmlSerializerFormat(Style=OperationFormatStyle.Document)]
public interface IEventsService
{
   [OperationContract(Name = "SaveEvent", Action = 
"http://www.thatindigogirl.com/EventsService/SaveEvent", ReplyAction = 
"http://www.thatindigogirl.com/2005/12/EventsService/SaveEventsResponse")]
    void SaveEvent(LinkItem item);

   [OperationContract(Name = "GetEvent", Action = 
"http://www.thatindigogirl.com/EventsService/GetEvent", ReplyAction = 
"http://www.thatindigogirl.com/EventsService/GetEventResponse")]
   LinkItem GetEvent();
 }

If the same type from Listing 6 were to be passed through the XmlSerializer, the resulting schema would differ from Listing 7 in a number of ways:

  • Only public fields and properties would be included in serialization
  • The order would not be alphabetical. Rather, it would match members’ order of appearance (reflection order) in the type.

Even better, you can decorate a serializable type with attributes to control naming conventions, data types and more. Listing 9 shows the same type, prepared specifically for the XmlSerializer.

*** Listing 9: A serializable type made friendly for the XmlSerializer

[Serializable]
[XmlRoot(ElementName="LinkItem", 
Namespace = "http://schemas.thatindigogirl.com/2005/12/LinkItems")]
public class LinkItem
{

  private long m_id;
  private string m_title;
  private string m_description;
  private DateTime m_dateStart;
  private DateTime? m_dateEnd;
  private string m_url;

  [XmlElement(ElementName = "DateStart", DataType = "date", Order = 3, 
IsNullable = false, Form = XmlSchemaForm.Qualified)]
  public DateTime DateStart
  {
    get { return m_dateStart; }
    set { m_dateStart = value; }
  }

  [XmlElement(ElementName = "DateEnd", DataType="date", Order = 4, 
IsNullable = true, Form = XmlSchemaForm.Qualified)]
  public DateTime? DateEnd
  {
    get { return m_dateEnd; }
    set { m_dateEnd = value; }
  }

  [XmlElement(ElementName = "Url", Order = 5, IsNullable = false, 
Form = System.Xml.Schema.XmlSchemaForm.Qualified)]
   public string Url
   {
     get { return m_url; }
     set { m_url = value; }
   }

   [XmlElement(ElementName = "Id", Order = 0, IsNullable = false, 
Form = System.Xml.Schema.XmlSchemaForm.Qualified)]
    public long Id
    {
      get { return m_id; }
      set { m_id = value; }
    }

    [XmlElement(ElementName="Title", Order = 1, IsNullable=false, 
Form=System.Xml.Schema.XmlSchemaForm.Qualified)]
    public string Title
    {
      get { return m_title; }
      set { m_title = value; }
    }

    [XmlElement(ElementName = "Description", Order = 2, IsNullable = false, 
Form = XmlSchemaForm.Qualified)]
    public string Description
    {
      get { return m_description; }
      set { m_description = value; }
    }
}

9. Limitations designing Windows Communication Foundation services with Serializable Types

Even if you have existing serializable types in your application, chances are the schema output will not be what you want for the WSDL contract without some adjustment. With the XmlFormatter, serializable types cannot be coerced into a schema with friendly naming conventions, nor can elements be omitted from serialization.

With the XmlSerializer you can control the resulting schema, but if you can’t edit the assembly that owns the type, you won’t be able to add attributes to exercise this control, so you have to hope that types are already decorated according to what their schema output should be. Otherwise, you need another approach for generating WSDL from your complex types.

We end up with the same issue I illustrated in Figure 1. That is, if types already exist in the business tier, and they aren’t ready for serialization according to the WSDL I want generated, I must find a way to receive a stream and map to my own types for efficiency.

10. Message Contracts

Message contracts are yet another way to control how WSDL is generated for your service. Message contracts still leverage data contracts and serializable types to emit schema for complex types, however they make it possible to explicitly control the SOAP message headers and body, from a single type. For example, Listing 10 illustrates a message contract, SaveEventRequest, for an inbound request that requires a custom SOAP header for a license key. The body of the request message still expects a LinkItem type as in earlier examples.

*** Listing 10: An example of a message contract for request and reply

[MessageContract]
public class SaveEventRequest
{
  private string m_licenseKey;
  private LinkItem m_linkItem;

  [MessageHeader(MustUnderstand=true)]
  public string LicenseKey
  {
    get { return m_licenseKey; }
    set { m_licenseKey = value; }
  }

  [MessageBody(Order=0)]
  public LinkItem LinkItem
  {
    get { return m_linkItem; }
    set { m_linkItem = value; }
  }
}

[MessageContract]
public class SaveEventResponse
{
}

The response message, SaveEventResponse, does not return any data currently, thus the type is empty. Using a message contract in this case makes it possible to later add members to the type, without impacting the service description. When the MessageContractAttribute is applied to a type, it can be used to serialize a SOAP message. Applying the MessageHeaderAttribute to data members creates one or more SOAP headers in the WSDL contract for this message. Applying the MessageBodyAttribute creates one or more SOAP body elements that can be ordered using the Order property of the attribute. Both headers and body elements can leverage data contracts or serializable types in their definition, resulting in the same output as I discussed earlier (with the same limitations).

11. Applying Message Contracts

Message contracts are associated with service operations as incoming or outgoing messages. In this example, Listing 11 illustrates the use of four custom message contracts: SaveEventRequest, SaveEventResponse, GetEventRequest, and GetEventResponse. Each is applied to the appropriate service operation, thus serialization for those operations is defined by the message contract allowing further control over the SOAP message for each.

*** Listing 11: Applying message contracts to services

[ServiceContract(Name="EventsService", Namespace="http://www.thatindigogirl.com/2005/12/EventsService")]
public interface IEventsService
{
  [OperationContract(Name = "SaveEvent", 
Action = "http://www.thatindigogirl.com/EventsService/SaveEvent", 
ReplyAction = "http://www.thatindigogirl.com/2005/12/EventsService/SaveEventsResponse")]
  SaveEventResponse SaveEvent(SaveEventRequest message);
  [OperationContract(Name = "GetEvent", 
Action = "http://www.thatindigogirl.com/EventsService/GetEvent", ReplyAction = 
 "http://www.thatindigogirl.com/EventsService/GetEventResponse")]
  GetEventResponse GetEvent(GetEventRequest message);
}

[ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]
public class EventsService: IEventsService
{
  private LinkItem m_linkItem;

  public SaveEventResponse SaveEvent(SaveEventRequest message)
  {
    if (message.LicenseKey != "XXX") throw new FaultException<string>("Invalid license key.");
    m_linkItem = message.LinkItem;
    return new SaveEventResponse();
  }

  public GetEventResponse GetEvent(GetEventRequest message)
  {
    if (message.LicenseKey != "XXX") throw new FaultException<string>("Invalid license key.");
    return new GetEventResponse(m_linkItem);
  }
}

Listing 11 also shows how the service implementation would interact with the message contract – which is really just to interact with it as a wrapper object for the SOAP message parameters, including custom headers.

12. Limitations designing Windows Communication Foundation services with Message Contracts

Message contracts provide a simple way to add custom SOAP headers to incoming and outgoing messages, and provide a way to remove wrapper elements from within the SOAP message to create a less RPC looking message. What the message contract does not solve is the issues we have with type serialization discussed in the previous two sections.

13. Summary: Code-first with WCF

Windows Communication Foundation service contract, data contracts, serializable types and message contracts all collectively provide code-first techniques for designing services. If you are not bound to pre-existing middle-tier objects and can design the service and middle-tier from scratch, you can adequately prepare data contracts to make WSDL generation simple. If you have serializable types that generate adequate schema, that can work as well. And, if you want to control the overall SOAP message creating a message contract will do just the trick.

In the next section I’ll talk about contract-first approaches that may help us to overcome the limitations of each code-first approach.

Contract-First – Powerful, but not so easy…still

Contract-first service design implies that you begin with a WSDL contract and reverse engineer that into a service. There are also other approaches that still fit with contract-first, but don’t put you into the realm of WSDL design from scratch. The most important aspect of contract-first is the message payload – the types passed with each message. For example, you may have an XML schema that perfectly describes the data elements expected to create an insurance form, therefore you want to receive a message that passes XML fitting that schema that has already been standardized between industry partners, possibly through a standards body (in the insurance industry that standards body would be ACORD). The question is, how can you generate a WSDL to associate that schema to the appropriate operation? And, how can you turn that message payload into business objects on the back-end?

In this section, I’ll explore the WCF constructs that support designing services from the other direction, leaving more flexibility in mapping messages to objects.

1. Working with raw messages

With Windows Communication Foundation, you can design a service contract that operates on raw messages. The Message type is an abstract base type in System.SeviceModel that gives you access to message headers, message body elements, the runtime state of the message (is it faulted?), other message properties, and version. This type also makes it possible to create messages from scratch at runtime, without a service proxy on the client side, and send them over an open channel.

2. Contracts, services and raw messages

If you want to work with raw messages within a service implementation, you can define a contract that deals in un-typed messages. Listing 12 shows an example of such a service contract.

*** Listing 12: A Windows Communication Foundation service contract using un-typed messages

[ServiceContract(Name="EventsService", Namespace="http://www.thatindigogirl.com/2005/12/EventsService")]
    public interface IEventsService
    {
        [OperationContract(Name = "SaveEvents", Action = 
   "http://www.thatindigogirl.com/2005/12/EventsService/SaveEvents", ReplyAction = 
   "http://www.thatindigogirl.com/2005/12/EventsService/SaveEventsResponse")]
        Message SaveEvents(Message message);

        [OperationContract(Name = "GetEvents", Action = 
   "http://www.thatindigogirl.com/2005/12/EventsService/GetEvents", ReplyAction = 
   "http://www.thatindigogirl.com/2005/12/EventsService/GetEventsResponse")]
        Message GetEvents(Message message);
    }

This contract allows the service to receive a Message and parse the headers and body of the message directly, building business objects by hand. One possible service implementation for this contract is shown in Listing 13.

*** Listing 13: Service implementation using un-typed messages.

    public class EventsService: IEventsService
    {
        private List<LinkItem> m_linkItems;

        public Message SaveEvents(Message message)
        {
            SaveEventsRequest saveEventsReq = new SaveEventsRequest(message.GetReaderAtBodyContents());
            m_linkItems = saveEventsReq.LinkItems;
            return Message.CreateMessage("http://www.thatindigogirl.com/2005/12/EventsService/SaveEventsResponse");
        }
        public Message GetEvents(Message message)
        {
            return new GetEventsResponse(m_linkItems);
        }
    }

The SaveEvents() operation will build a LinkItem list from the inbound message. All deserialization is handled by the service implementation since there are no data contracts or serializable types known to the service contract. The SaveEventsRequest type, which derives from System.ServiceModel.Message, handles this deserialization process.

The return value from the SaveEvents() operation also expects a Message, however no data is returned so the message body is empty. In this case, rather than deriving from Message to create a SaveEventsResponse type, a raw message is created using the static CreateMessage() method. This creates a message that can be manually populated with headers, properties and body content, but in this case I am only setting the Action header by passing it to the overloaded constructor.

3. How does the Message type help with contract-first?

If a particular XML schema is expected for a service operation’s parameters or return value, a contract that uses Message as the data type for both can literally receive any SOAP message and let your code to the validation and serialization/deserialization. If the WSDL contract is generated using WSDL-first development tools like XMLSpy provides, your service need not be responsible for generating WSDL, yet you can write code that will match the WSDL with which your service should be compliant.

In this example, the SaveEventsRequest type derives from Message and provides a constructor to receive the body XML in the form of an XmlReader. Listing 14 shows a version of this implementation that parses the XML and generates a LinkItem list.

** Listing 14: Parsing the incoming SOAP message.

  public SaveEventsRequest(XmlReader xmlReader)
        {
            XmlDictionaryReader reader = XmlDictionaryReader.CreateDictionaryReader(xmlReader);
            bool isValidRoot = reader.IsStartElement("LinkItems");

            if (!isValidRoot)
            {
                throw new XmlException("ExpectedElementMissing: Top level element must be LinkItems.");
            }

            XmlDocument doc = new XmlDocument();
            XmlElement root = doc.ReadNode(reader) as XmlElement;

            for (int i = 0; i < root.ChildNodes.Count; i++)
            {
                XmlElement elemLink = root.ChildNodes[i] as XmlElement;
                LinkItem item = new LinkItem();

                for (int j = 0; j < elemLink.ChildNodes.Count; j++)
                {
                    XmlElement elemItem = elemLink.ChildNodes[j] as XmlElement;

                    if (elemItem != null)
                    {

                        if (elemItem.LocalName == "Id")
                        {
                            item.Id = int.Parse(elemItem.InnerText);
                        }
                        else if (elemItem.LocalName == "Title")
                        {
                            item.Title = elemItem.InnerText;
                        }
                        else if (elemItem.LocalName == "Description")
                        {
                            item.Description = elemItem.InnerText;
                        }
                        else if (elemItem.LocalName == "DateStart")
                        {
                            item.DateStart = DateTime.Parse(elemItem.InnerText);
                        }
                        else if (elemItem.LocalName == "DateEnd")
                        {
                            item.DateEnd = DateTime.Parse(elemItem.InnerText);
                        }
                        else if (elemItem.LocalName == "Url")
                        {
                            item.Url = elemItem.InnerText;
                        }
                    }

                }
                this.m_linkItems.Add(item);
            }
        }

To write a custom message, you can derive from Message and override the OnWriteBodyContents() method to interact with the WCF runtime as the channel attempts to serialize the message. An alternative to deriving from Message is to derive from BodyWriter instead (you still override OnWriteBodyContents()). The former is useful for customizing headers and other properties of the SOAP message, while the latter is geared at simply providing implementation for SOAP body serialization and deserialization. Listing 14 illustrates an implementation for OnWriteBodyContents.

*** Listing 15: Parsing the incoming SOAP message.

protected override void OnWriteBodyContents(System.Xml.XmlDictionaryWriter writer)
{
            writer.WriteStartElement("LinkItems");

            foreach (LinkItem item in this.m_linkItems)
            {
                writer.WriteStartElement("LinkItem");

                writer.WriteStartElement("Id");
                writer.WriteValue(item.Id);
                writer.WriteEndElement();

                writer.WriteElementString("Title", item.Title);
                writer.WriteElementString("Description", item.Description);

                writer.WriteStartElement("DateStart");
                writer.WriteValue(item.DateStart);
                writer.WriteEndElement();

                writer.WriteStartElement("DateEnd");
                writer.WriteValue(item.DateEnd);
                writer.WriteEndElement();

                writer.WriteElementString("Url", item.Url);

                writer.WriteEndElement(); //LinkItem

            }
            writer.WriteEndElement(); //LinkItems
}

4. Limitations designing Windows Communication Foundation services with raw messages

Working with raw messages makes it easy to control type serialization and deserialization, and interact with the process of building the SOAP message including headers, body and other message properties. For a service this allows you to receive messages in the raw and generate types without relying on WCF to handle automatic serialization. This gives you more control when working with business objects that are not data contracts or serializable.

You cannot generate a useful WSDL contract from a service contract that uses Message for operation parameters and return values. WCF still generates the WSDL contract, but the resulting messages will not articulate the types required. Instead, service operations associate schema type xs:any with each message. Using the automatically generated WSDL will not be useful to clients either, since the resulting proxies generated with svcutil.exe will not yield any useful type information.

In short, for WSDL contracts generated contract-first, implementing services that work with raw messages is very useful on the service side. On the client side it may have singular benefits such as building messages that conform to the WSDL while working with your own client-side code to work with XML representations of the data. When schemas for complex types are too complicated to automatically generate objects for (in the service or client implementation), this is a nice capability.

5. IXmlSerializable

Up to now, this article has talked about the two extremes for generating service contracts: working with serializable objects, and working with raw XML. There is yet one more option that solves one core problem I keep harping on: How can you receive XML and turn it into objects and still get a proper schema representation?

Using IXmlSerializable you can do just that. You can expose types that implement IXmlSerializable in your service contract, even if they are not data contracts or otherwise marked with the SerializableAttribute. With IXmlSerializable you can provide an XML schema to the runtime for generating WSDL, supplying schemas through metadata exchange. In the meantime you have full control over mapping the incoming XML stream into objects.

6. Windows Communication Foundation service contracts and IXmlSerializable

Designing a contract for a service implies you are defining the service description or WSDL. Using code-first approaches, types provided in the contract are converted to XML schemas and included in the WSDL document. When you already have the schema for your incoming or outgoing messages, you can create a type that more or less exposes that schema, and use that type in the contract definition. For example, Listing 16 illustrates a contract that appears to expose a LinkItem type, and it looks similar to earlier examples.

*** Listing 16: A service contract using IXmlSerializable types

[ServiceContract(Name="EventsService", Namespace="http://www.thatindigogirl.com/2005/12/EventsService")]
    public interface IEventsService
    {
        [OperationContract(Name = "SaveEvent", Action = 
"http://www.thatindigogirl.com/2005/12/EventsService/SaveEvent", ReplyAction = 
"http://www.thatindigogirl.com/2005/12/EventsService/SaveEventResponse")]
        void SaveEvent(LinkItem item);

        [OperationContract(Name = "GetEvent", Action = 
"http://www.thatindigogirl.com/2005/12/EventsService/GetEvent", ReplyAction = 
"http://www.thatindigogirl.com/2005/12/EventsService/GetEventResponse")]
        LinkItem GetEvent();
    }

In this case, the LinkItem type actually implements IXmlSerializable, which means it provides an implementation for WriteXml() and ReadXml(), the two functions invoked by the XmlFormatter and XmlSerializer during serialization and deserialization. For contract and proxy generation the runtime will call GetSchema() (also part of the interface), expecting you to return an XSD that represents the type. The only issue with GetSchema() is that it actually creates an instance of the type to request the schema. So, in lieu of this implementation, you can mark the type with the XmlSchemaProviderAttribute as I have done, which allows you to provide a static method that returns the type’s schema. Listing 17 illustrates a type that implements this.

*** Listing 17: A service contract using IXmlSerializable types.

    [XmlSchemaProvider("GetSchema")]
    public class LinkItem : IXmlSerializable
    {
        static string ns = "http://schemas.thatindigogirl.com/2005/12/LinkItems";

 private long m_id;
        private string m_title;
        private string m_description;
        private DateTime m_dateStart;
        private DateTime m_dateEnd;
        private string m_url;

 // default constructor...

 // public property wrappers...

        public static XmlQualifiedName GetSchema(XmlSchemaSet schemaSet)
        {

            XmlSchema shema = XmlSchema.Read(new StringReader("<xs:schema xmlns:tns='" +
          ns + "' xmlns:xs='http://www.w3.org/2001/XMLSchema' targetNamespace='" + ns +
          "' elementFormDefault='qualified' attributeFormDefault='unqualified'>
   <xs:complexType name='item'><xs:sequence><xs:element name='Id' type='xs:string' 
   nillable='false'/><xs:element name='Title' type='xs:string' nillable='false'/>
   <xs:element name='Description' type='xs:string' nillable='false'/>
   <xs:element name='DateStart' type='xs:date' nillable='false'/><xs:element 
   name='DateEnd' type='xs:date' nillable='true' minOccurs='0'/><xs:element name='Url' 
   type='xs:string' nillable='false' minOccurs='0'/></xs:sequence></xs:complexType>
   </xs:schema>"), null);
            schemaSet.XmlResolver = new XmlUrlResolver();
            schemaSet.Add(shema);

            return new XmlQualifiedName("item", ns);
        }

        void IXmlSerializable.WriteXml(System.Xml.XmlWriter writer)
        {

            writer.WriteStartElement("item");

            writer.WriteStartElement("Id", ns);
            writer.WriteValue(this.Id);
            writer.WriteEndElement();

            writer.WriteElementString("Title", ns, this.Title);
            writer.WriteElementString("Description", this.Description);

            writer.WriteStartElement("DateStart", ns);
            writer.WriteValue(this.DateStart);
            writer.WriteEndElement();

            writer.WriteStartElement("DateEnd", ns);
            writer.WriteValue(this.DateEnd);
            writer.WriteEndElement();

            writer.WriteElementString("Url", ns, this.Url);

            writer.WriteEndElement(); //LinkItem


        }

        System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
        {
            throw new NotImplementedException("IXmlSerializable.GetSchema() 
is not implemented. Use static GetSchema() instead.");
        }



        void IXmlSerializable.ReadXml(System.Xml.XmlReader xmlReader)
        {
            XmlDictionaryReader reader = XmlDictionaryReader.CreateDictionaryReader(xmlReader);
            bool isValidRoot = reader.IsStartElement("item");

            if (!isValidRoot)
            {
                throw new XmlException("ExpectedElementMissing: Top level element must be LinkItem.");
            }

            XmlDocument doc = new XmlDocument();
            XmlElement root = doc.ReadNode(reader) as XmlElement;

            for (int j = 0; j < root.ChildNodes.Count; j++)
            {
                XmlElement elemItem = root.ChildNodes[j] as XmlElement;

                if (elemItem != null)
                {

                    if (elemItem.LocalName == "Id")
                    {
                        this.Id = int.Parse(elemItem.InnerText);
                    }
                    else if (elemItem.LocalName == "Title")
                    {
                        this.Title = elemItem.InnerText;
                    }
                    else if (elemItem.LocalName == "Description")
                    {
                        this.Description = elemItem.InnerText;
                    }
                    else if (elemItem.LocalName == "DateStart")
                    {
                        this.DateStart = DateTime.Parse(elemItem.InnerText);
                    }
                    else if (elemItem.LocalName == "DateEnd")
                    {
                        this.DateEnd = DateTime.Parse(elemItem.InnerText);
                    }
                    else if (elemItem.LocalName == "Url")
                    {
                        this.Url = elemItem.InnerText;
                    }
                }

            }

        }
    }

7. IXmlSerializable and Proxies

With IXmlSerializable you have done your job with service design. You have exposed a specific schema for each operation, and you have preserved your ability to work with non-serializable complex types on the service-side. The service world is beautiful. For clients, proxy generators are still going to try and turn that schema into objects, because that’s what proxy generators do. Therefore, you will end up with a potentially messy object model.

8. Limitations designing Windows Communication Foundation services with IXmlSerializable

IXmlSerializable is incredibly useful for designing services that incorporate existing schema that may be too complex to reverse engineer into business objects. However, there still exists a problem for client consuming these services. Proxy generation for complex schemas must be addressed as well in order for clients to consume these services with the option to generate their own XML that matches the type schema. Currently, proxy generation always attempts to turn schema into objects, when the client may prefer to work with XML directly, and perform client side schema validation prior to sending messages to the service.

Conclusion

When you select your approach for designing a Windows Communication Foundation service contract, keep in mind the following:

  • What control do you have over existing business objects and their serialization?
  • Do you want to litter the business tier with data contracts to simplify service design?
  • Do you have existing schemas you need to support that require custom code to map between XSD and business objects?
  • Do you require custom SOAP headers and other message properties?

The answers to these questions will influence your approach to service design because they directly affect your choice of code-first or contract-first support in WCF. I have only begun at the end of this article to explore the extensibility mechanisms on the contract-first side of the equation…to keep things simple, but you can also do things like create reusable components that decouple header serialization from the service implementation; create custom components that generate WSDL and handle metadata exchange at runtime; and do things like send raw messages across channels without ever creating a proxy. The point is you can design services the easy way and achieve code-first or contract-first results…or exercise as much control as you need with some of the more advanced extensibility points. The tooling around this functionality is best for code-first, today…but it can only get better.

Download the source code for this article

About the author: Michele Leroux Bustamante is a Chief Architect with IDesign, Microsoft Regional Director for San Diego, Microsoft MVP for Web Services and a BEA Technical Director. In addition, Michele is a member of the board of directors for the International Association of Software Architects (IASA). At IDesign Michele rovides high-end architecture consulting services, training and mentoring. Her specialties include architecture design for robust, scalable and secure .NET architecture; localization; Web applications and services; and interoperability between .NET and Java platforms. Michele is a member of the INETA; a frequent conference presenter at major technology conferences such as Tech Ed, PDC, SD and Dev Connections; conference chair for SD’s Web Services track; and a regularly published author. Michele’s next book is Windows Communication Framework Jumpstart for O’Reilly, due out in early 2006. Reach her at www.idesign.net or visit her blog at www.dasblonde.net.


This was first published in March 2008
This Content Component encountered an error

0 comments

Oldest 

Forgot Password?

No problem! Submit your e-mail address below. We'll send you an email containing your password.

Your password has been sent to:

-ADS BY GOOGLE

SearchCloudComputing

SearchSoftwareQuality

SearchSOA

TheServerSide

SearchCloudApplications

Close