Export (0) Print
Expand All

WCF Essentials: Contracts

Articles in this series

Download the code for this article

Applications have unique needs for exposing functionality through service contracts and for choosing the right mechanism for serializing complex types for each service operation. This chapter will review core considerations for contract design including designing service contracts, choosing between complex type serialization formats, and when to use the XmlSerializer or message contracts.

Service Contracts

Service contracts are defined by applying the ServiceContractAttribute and OperationContractAttribute to an interface or class – preferably the former since this supports the separation of metadata from implementation for sharing libraries, and makes it possible for a service to implement multiple contracts. At a minimum you should always provide a namespace for the service contract. The default namespace is http://tempuri.org. Providing a namespace specific to your company or application disambiguates message on the wire and provides a mechanism for contract versioning (to be discussed in a later chapter). This namespace is usually consistent across all service contracts for the application. It can be any string but is traditionally a Uri representative of the company or application domain and includes a year and month to support versioning scenarios.

Both the ServiceContractAttribute and OperationContractAttribute expose a Name property that is useful for providing explicit names for contracts and service operations, respectively. Explicit naming decouples CLR naming conventions from the contract – and prevents problems that can be introduced if developers re-factor the interface or its method names. These practices are illustrated in as Figure 1.

Figure 1: A service contract using explicit naming

[ServiceContract(Name="ISimpleService",

Namespace="urn:WCFEssentials/Samples/2008/12")]

public interface ISimpleService

{

    [OperationContract (Name="SendMessage")]

    string SendMessage(string message);

}

Callback contracts used for two-way communication infer the same namespace as the service contract they are associated with. Callback contracts don’t require a ServiceContractAttribute but in the event the callback contract will also be implemented as a service – as in many pub-sub solutions – it is best to explicitly include it with the correct namespace otherwise the service implementation will use the default namespace. Callback contracts should also only expose one-way operations as shown in Figure 2 – since the service should not care about any exceptions or response This also prevents deadlocks in a single-threaded service.

Figure 2: Implementing a callback contract

[ServiceContract(Name="ISimpleService", Namespace="urn:WCFEssentials/Samples/2008/12", CallbackContract=typeof(ISimpleServiceCallback))]

public interface ISimpleService

{

    [OperationContract (Name="SendMessage")]

    void SendMessage(string message);

}

[ServiceContract(Namespace = "urn:WCFEssentials/Samples/2008/12")]

public interface ISimpleServiceCallback

{

    [OperationContract(Name = "Callback", IsOneWay=true)]

    void Callback(string message);

}

Naming conventions for service operation parameters and return types can be controlled using the MessageParameterAttribute as shown in Figure 3. This should only matter if you want to control the XML element naming convention, or if you are concerned about developers refactoring parameter names and accidentally altering the contract.

Figure 3: Controlling parameter and return value naming conventions

[ServiceContract(Name="ISimpleService", Namespace="urn:WCFEssentials/Samples/2008/12")]

public interface ISimpleService

{

    [OperationContract (Name="SendMessage")]

    [return:MessageParameterAttribute(Name="MessageOut")]

    string SendMessage([MessageParameterAttribute(Name = "MessageIn")]string message);

}

Other aspects of the service contract relate to specific features such as exception handling, sessions, transactions and web programming and are discussed in other chapters of this whitepaper.

In the accompanying code download for this chapter, the following directories contain samples that illustrate explicit service contracts and callback contract implementations: WCFEssentials\Contracts\ServiceContract, WCFEssentials\Contracts\CallbackContract.

Choosing Complex Type Serialization Requirements

When you define a service contract you must also define the parameters and return types for each service operation. Complex types employed in a service contract can be data contracts, Plain Old CLR Object (POCO) types, serializable types, IXmlSerializable types or types decorated for the XmlSerializer. The choice between these complex type formats is driven by the following:

  • Will you be creating new complex types for a new application?
  • Will you be using complex types defined in pre-existing assemblies?
  • Are your predefined types data contracts, or serializable types?
  • Are you migrating an ASMX service to WCF?
  • Do you have existing XSD schemas that define wire representation of complex types?

Figure 4 illustrates from a high level how to choose between these serialization options for a service contract. Data contracts are preferred if you are starting from scratch, since you are likely to let your types produce the schema for clients (code-first) as opposed to starting with a schema (contract-first). If you have a preexisting WSDL definition you can use SvcUtil to reverse engineer that into a service contract with associated data contracts. If the data contract representation of a complex type schema is too complex you can use IXmlSerializable types to map between schema and complex types. If you are migrating ASMX services and already have existing complex types that have been decorated with XmlSerializer attributes – it may be more productive to continue using those types in the short term and use the XmlSerializer instead of the DataContractSerializer. This allows you to work with existing types without converting them to data contract. If you can, however, it is better to try to convert those types to data contracts by pushing the ASMX WSDL through SvcUtil. Serializable types can be used in the service contract – and if they are located in assemblies that you cannot edit it may not be possible to convert those types to data contracts. If types in pre-existing assemblies are not serializable or data contracts you can fall back on POCO types – but this is not the best option.

Figure 4: Choosing complex type serialization requirements

Message contracts are orthogonal to your choices for complex type serialization. If message contracts are used, they will contain the appropriate complex types as message headers, or as body members to represent parameters or return types.

Data Contracts

Data contracts are defined with the DataContractAttribute and DataMemberAttribute. At a minimum you should always provide a namespace for the data contract. As with service contracts, this namespace helps to disambiguate types on the wire and provides a versioning mechanism. This namespace is usually consistent across all data contracts for the application, and is often similar to the service contract namespace using a “schemas” prefix. Both the DataContractAttribute and DataMemberAttribute expose a Name property that is useful for providing explicit names for complex types and properties, respectively. As with service contracts explicit naming decouples CLR naming conventions from the contract and prevents re-factoring problems.

I recommend that you apply the DataMemberAttribute to property accessors instead of members even if this means creating private and protected accessors. You should also decide up front which data members are required, and what order members should appear in the schema. Then, supply explicit values for IsRequired and Order so that the impact of future modifications is clear to all developers.

Figure 5 illustrates these recommended practices for a data contract implementation. Note that the example uses the C# 3.0 automatic properties feature.

Figure 5: Recommended practices for data contract implementations

[DataContract(Namespace = "urn:WCFEssentials/Schemas/2008/12")]

public class LinkItem

{

    [DataMember(Name = "Id", IsRequired = false, Order = 0)]

    public long Id { get; set; }

    [DataMember(Name = "Title", IsRequired = true, Order = 1)]

    public long Title { get; set; }

    [DataMember(Name = "Description", IsRequired = true, Order = 2)]

    public long Description { get; set; }

    [DataMember(Name = "DateStart", IsRequired = true, Order = 3)]

    public DateTime? DateStart { get; set; }

    [DataMember(Name = "DateEnd", IsRequired = false, Order = 4)]

    public DateTime? DateEnd { get; set; }

    [DataMember(Name = "Url", IsRequired = false, Order = 5)]

    public string Url { get; set; }

}

If you create a custom collection type you will use the CollectionDataContractAttribute instead of the DataContractAttribute. This attribute lets you control the naming convention for items, and in the case of dictionaries lets you control key and item names. With all of the rich collection classes available in System.Collections.Generic it is unlikely that you’ll need to create a custom collection. The CollectionDataContractAttribute is still, however, very useful for customizing key and item names for the predefined collections and dictionaries. As illustrated in Figure 6 you can inherit your collection type of choice and apply the attribute without supplying any further implementation – and then use this custom type in your service contracts or data contracts.

Figure 6: CollectionDataContractAttribute applied to List<T>

[CollectionDataContract(Namespace = "urn:WCFEssentials/Schemas/2008/12",

ItemName="MyItemName")]

public class MyStrings: List<string>{}

[CollectionDataContract(Namespace = "urn:WCFEssentials/Schemas/2008/12",

KeyName="MyKeyName", ItemName = "MyItemName", ValueName = "MyValueName")]

public class MyDictionary : Dictionary<string, string> {}

Enumerations do not require any attributes to be serializable – however you can apply the DataContractAttribute if you want to control naming convention for enum members, or exclude an enum element from the public contract. Figure 7 illustrates suppressing one enum element by applying the DataContractAttribute to the enumeration type and applying the EnumMemberAttribute to opt-in only two of the three enum elements.

Figure 7: Suppressing enum elements from the public contract

[DataContract(Namespace = "urn:WCFEssentials/Schemas/2008/12")]

public enum LinkItemCategory

{

  [EnumMember]

  Whitepaper,

  [EnumMember]

  Article,

  Webcast

}

In the accompanying code download for this chapter, the following directories contain samples that illustrate explicit data contracts, collection data contracts, and enumerations: WCFEssentials\Contracts\DataContract, WCFEssentials\Contracts\CollectionDataContract, WCFEssentials\Contracts\EnumDataContract.

POCO Types

As of .NET 3.5 Service Pack 1 (SP1) the DataContractSerializer supports POCO types. That means you can use any CLR type as a service contract parameter or return type – without applying the DataContractAttribute. While this is certainly easier since it provides a similar experience to ASMX services – this approach has some limitations:

  • Only public fields and properties are included in serialization.
  • You have no control over serialization naming conventions and order, nor any way to define required elements.
  • You can’t control the namespace for the schema definition.

I don’t recommend using POCO for mission-critical business applications.

In the accompanying code download for this chapter, the following directory contains a sample illustrating POCO objects: WCFEssentials\Contracts\POCO.

Known Types

Known types provide a way to support polymorphism in service contracts. For example, a service contract can use a common base type as a parameter or return type and with the appropriate known type associations derived types will be understood by the DataContractSerializer. If you associate known types with the base type using the KnownTypeAttribute – all service contracts where the base type is employed will support the known types. Figure 8 shows a service contract exposing the base LinkItem type which has two known types – WebcastItem and ArticleItem.

Figure 8: Associating known types with the base type definition

[ServiceContract(Namespace = "urn:WCFEssentials/Samples/2008/12")]

public interface IWebContentManager

{

    [OperationContract]

    void AddLink(LinkItem item);

    [OperationContract]

    List<LinkItem> GetLinks();

}

[KnownType(typeof(WebcastItem))]

[KnownType(typeof(ArticleItem))]

[DataContract(Namespace = "urn:WCFEssentials/Schemas/2008/12")]

public class LinkItem

{

    [DataMember(Name = "Id", IsRequired = false, Order = 0)]

    public long Id { get; set; }

    [DataMember(Name = "Title", IsRequired = true, Order = 1)]

    public long Title { get; set; }

    [DataMember(Name = "Description", IsRequired = true, Order = 2)]

    public long Description { get; set; }

    [DataMember(Name = "DateStart", IsRequired = true, Order = 3)]

    public DateTime? DateStart { get; set; }

    [DataMember(Name = "DateEnd", IsRequired = false, Order = 4)]

    public DateTime? DateEnd { get; set; }

    [DataMember(Name = "Url", IsRequired = false, Order = 5)]

    public string Url { get; set; }

}

[DataContract(Namespace = "urn:WCFEssentials/Schemas/2008/12")]

public class WebcastItem : LinkItem {…}

[DataContract(Namespace = "urn:WCFEssentials/Schemas/2008/12")]

public class ArticleItem : LinkItem {…}

The same result can be achieved declaratively as shown in Figure 9.

Figure 9: Declaratively associating known types with the base type

  <system.runtime.serialization>

    <dataContractSerializer>

         <declaredTypes>

        <add type="WCFEssentials.Common.Entities.LinkItem, WCFEssentials.Common.Entities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">

          <knownType type="WCFEssentials.Common.Entities.ArticleItem, WCFEssentials.Common.Entities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />

          <knownType type="WCFEssentials.Common.Entities.WebcastItem, WCFEssentials.Common.Entities, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />

        </add>

      </declaredTypes>

    </dataContractSerializer>

  </system.runtime.serialization>

As an alternative you can associate named types with specific service operations, or with an entire service contract using the ServiceKnownTypeAttribute – as shown in Figure 10.

Figure 10: Associating known types with a service contract or service operation

[ServiceContract(Namespace = "urn:WCFEssentials/Samples/2008/12")]

[ServiceKnownType(typeof(WebcastItem))]

[ServiceKnownType(typeof(ArticleItem))]

public interface IWebContentManagerService

{

    [OperationContract]

    void AddLink(LinkItem item);

    [OperationContract]

    List<LinkItem> GetLinks();

}

[ServiceContract(Namespace = "urn:WCFEssentials/Samples/2008/12")]

public interface IWebContentManagerService

{

    [OperationContract]

    void AddWebcastLink(WebcastItem item);

    [OperationContract]

    void AddArticleLink(ArticleItem item);

    [OperationContract]

    [ServiceKnownType(typeof(WebcastItem))]

    [ServiceKnownType(typeof(ArticleItem))]

    List<LinkItem> GetLinks();

}

Each technique has its pros and cons – but I generally recommend the following:

  • If all known types are valid across all service contracts that expose the base type, apply known types to the base type.
  • If you expect known types to change frequently and need a more dynamic approach use the declarative technique and add types as needed.
  • If different services support different known types, apply known types to service contracts or operations.
  • If known types will change dynamically at runtime, both the KnownTypeAttribute and ServiceKnownTypeAttribute support dynamic loading of known types.

In the accompanying code download for this chapter, the following directories contain samples that illustrate working with known types: WCFEssentials\Contracts\KnownTypeAttribute, WCFEssentials\Contracts\ServiceKnownTypeAttribute, WCFEssentials\Contracts\DeclarativeKnownTypes, WCFEssentials\Contracts\DynamicKnownTypes.

Serializable Types

Although you should try to employ data contracts in your service contracts, types marked with the SerializableAttribute (see Figure 11) are supported by the DataContractSerializer.

Figure 11: A complex types marked with the SerializableAttribute

[Serializable]

public class LinkItem

{

    private long m_id;

    private long m_title;

    private long m_description;

    private DateTime? m_DateStart;

    private DateTime? m_DateEnd;

    private string m_url;

    // property accessors

}

You should note the following about serializable types:

  • The resulting serialization is wire-format – which means that all fields are serialized regardless of their accessibility.
  • Serializable types can participate in type hierarchies with data contracts.
  • Types can be marked with the SerializableAttribute as well as the DataContractAttribute. This is useful if the type is used in service contracts as well as serialized by other parts of the .NET Framework such as the ASP.NET session.

XmlSerializer

The XmlSerializer can be useful in a few scenarios. A common scenario is when you are migrating ASMX service implementations to WCF and you have a number of predefined complex types in assemblies that you want to leverage intact with the new implementation. Since ASMX services rely on the XmlSerializer to serialize those complex types, you would want your WCF services to use the XmlSerializer. This can be done by applying the XmlSerializerFormatAttribute as shown in Figure 12.

Figure 12: Applying the XmlSerializerFormatAttribute

[ServiceContract(Namespace = "urn:WCFEssentials/Samples/2008/12")]

[XmlSerializerFormat]

public interface IArticlesManagerService

{

  [OperationContract]

  void AddArticleLink(LinkItem item);

  [OperationContract]

  List<LinkItem> GetArticleLinks();

}

This approach also means that complex types decorated with XmlSerializer attributes such as the XmlElementAttribute and XmlAttributeAttribute will serialize as expected.

Another case where the XmlSerializer may be useful is when you need greater control over the XML schema generated for a complex type. For example the XmlSerializer XmlElementAttribute lets you choose from three XSD schema date and time types – date, time, or dateTime – while the DataContractSerializer only supports dateTime. This is illustrated in Figure 13.

Figure 13: A complex type decorated with XmlSerializer attributes

[XmlRoot(Namespace = "urn:WCFEssentials/Schemas/2008/12")]

public class LinkItem

{

    [XmlElement(IsNullable = false, Order = 0)]

    public long Id {get; set;}

    [XmlElement(IsNullable = false, Order = 1)]

    public string Title {get; set;}

    [XmlElement(IsNullable = false, Order = 2)]

    public string Description {get; set;}

    [XmlElement(DataType = "date", IsNullable = false, Order = 3)]

    public DateTime DateStart {get; set;}

    [XmlElement(DataType = "date", IsNullable = false, Order = 4)]

    public DateTime DateEnd {get; set;}

    [XmlElement(IsNullable = false, Order = 5)]

    public string Url {get; set;}

    [XmlIgnore]

    public string Tags {get; set;}

}

Another XmlSerializer attribute, the XmlAttributeAttribute, supports mapping public fields or properties to XML attributes instead of XML elements – something not supported by the DataContractSerializer. Typically this level of detail is not critical if you are starting from scratch, but it is relevant when you have existing XSD schemas to which your messaging must conform, and is often a consideration for interoperability with other platforms.

If you can I recommend you reverse engineer your ASMX services into WCF service contracts and data contracts using SvcUtil.

In the accompanying code download for this chapter, the following directory contains a sample that illustrates working with the XmlSerializer: WCFEssentials\Contracts\XmlSerializer.

IXmlSerializable

If you have a predefined XML schema that you need to support as a parameter or return type, you may find that this schema does not reverse engineer into a very friendly data contract object model for its complexity. In cases like this you can use IXmlSerializable types to handle mapping between the schema and your complex types. IXmlSerializable gives you the ability to do the following:

  • Associate the XML schema with the appropriate parameter or return type. This is done by implementing the GetSchema() method of the IXmlSerializable type, or by applying the XmlSchemaProviderAttribute. The latter is best since it allows for a static method to return the schema to the runtime when the service description is being produced – rather than an instance method which requires the IXmlSerializable type to be constructed.
  • Process incoming XML when the ReadXml() method is called by the runtime and construct the appropriate complex types.
  • Write outgoing XML from the appropriate complex type instances when the WriteXml() method is called by the runtime.

Figure 14 illustrates the core implementation requirements of an IXmlSerializable type, and how that type is employed in a service contract and service type implementation. As far as the service implementation goes, it interacts with the IXmlSerializable type to access complex types constructed by ReadXml(), and to supply complex types for WriteXml().

Figure 14: A partial view of an IXmlSerializable implementation

[XmlSchemaProvider("GetSchema")]

public class LinkItemSerializer: IXmlSerializable

{

    public LinkItem LinkItem {get; set;}

    public LinkItemSerializer(LinkItem item)

    {

        this.LinkItem = item;

    }

    public static XmlQualifiedName GetSchema(XmlSchemaSet schemaSet) {…}

    #region IXmlSerializable Members

    System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema(){…}

    void IXmlSerializable.ReadXml(System.Xml.XmlReader reader){…}

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

    #endregion

}

In the accompanying code download for this chapter, the following directory contains a sample that illustrates working with IXmlSerializable: WCFEssentials\Contracts\IXmlSerializable.

Message Contracts

Message contracts give you more control over the actual SOAP message for a service operation request or reply. When you use message contracts you supply a type decorated with the MessageContractAttribute as the only parameter to a service operation and as the return type as shown in Figure 15.

Figure 15: A service contract that employs message contracts

[ServiceContract(Namespace = "urn:WCFEssentials/Samples/2008/12")]

public interface IArticlesManagerService

{

    [OperationContract]

    AddArticleLinkResponse AddArticleLink(AddArticleLinkRequest requestMessage);

    [OperationContract]

    GetArticleLinksResponse GetArticleLinks(GetArticleLinksRequest requestMessage);

}

Normally you will create request and reply message contracts, paired for each operation. By applying the MessageHeaderAttribute or the MessageBodyMemberAttribute to properties of the message contract you can create message headers and body element for serialization in a request or reply.

Message contracts are particularly useful in the following scenarios:

  • When you want to include custom message headers in the request or reply.
  • Disable message wrapping for interoperability with platforms that do not wrap messages.
  • Supply multiple body members in the reply.

Figure 16 shows the request and reply message contracts from Figure 15 illustrating these features.

Figure 16: Request and reply message contracts

[MessageContract(IsWrapped = false)]

public class GetArticleLinksRequest

{

    [MessageHeader]

    public string LicenseKey {get; set;}

}

[MessageContract(IsWrapped = false)]

public class GetArticleLinksResponse

{

    public GetArticleLinksResponse(List<LinkItem> item)

    {

        this.Items = item;

    }

    [MessageBodyMember]

    public List<LinkItem> Items {get; set;}

}

You can also control message protection (encryption and signing) of individual header and body elements although this is not a common requirement.

In the accompanying code download for this chapter, the following directory contains a sample that illustrates message contracts: WCFEssentials\Contracts\MessageContract.

Recommendations: Contracts

  1. Implement service contracts on interfaces not classes.
  2. Always provide a namespace to ServiceContractAttribute and DataContractAttribute.
  3. Provide explicit names for service contracts, service operations, data contracts and data members to avoid refactoring errors. As an alternative to this, be sure that developers know the impact of refactoring these types.
  4. Use the same namespace for service contracts and associated callback contracts.
  5. Implement only one-way calls in callback contracts.
  6. Prefer data contracts for complex type serialization and avoid POCO (attribute-free data contract) types.
  7. Apply the DataMemberAttribute to property accessors instead of fields and, at a minimum, supply explicit values for IsRequired and Order.
  8. Use CollectionDataContractAttribute to control key and item names for collection and dictionary types.
  9. Apply known types to the appropriate scope and use dynamic known types only when the application does not require versioning as new types are introduced.
  10. Use the XmlSerializer to help with ASMX migration only when it is prohibitive to reverse engineer complex types into data contracts.
  11. When using the XmlSerializer use it for the entire service contract, not for individual operations.

Show:
© 2015 Microsoft