Wednesday, November 22, 2006

WCF Contracts: Separating Structure from Data

In my last post about designing WCF data contracts, I showed how to make a simple, straightforward hierarchical contract. In this post, I will refine the contract to make the building blocks of the data contract more reusable and how to adhere to WS-I basic profile best practices wrt naming of collections.

The data contract design of my last post is well suited from getting all data about a domain object structure in one go. It can also be used to add/modify nodes to/in the structure; but as each node data contract contains a combination of node metadata and collections of related items, it is not well suited for operations that inserts or updates items in the structure. The XSD schema of the data contract has no way of conveying what will happen with any collection items contained in the data when inserting a new ProjectDocuments node. The service might just ignore them (the 'easy' way), but this will add concealed semantics to the operations and violates the "share contract, not class/implementation" tenet of SOA. It would be better if the contract was unambiguous and easily understandable for the service consumers (the 'simple' way).

So to refactor the 'project documents' data contract into something that is well suited for both retrieving and modifying data, separating structure from data is needed. The metadata of each node in the hierarchy must be extracted into a new data contract, that becomes just another element in the node along with the collections. The new contract for the metadata is what we needed to make the schemas for insert and update operations simpler to use. In addition, the service will now have less subtle semantics.

The new ProjectDocuments contract using the new DocumentCategory data contract looks like this (click to enlarge):
The takeaway is that you should "normalize" your data contracts to some extent to make them more reusable across data contracts, messages and operations.

My colleague Anders Norås made me aware that naming collections ArrayOfXxx is not according to WS-I basic profile recommendations. WCF supports specifying the both collection element name and the collection item element name using the [CollectionDataContract] attribute. This attribute cannot, however, be applied to class members, only to a whole class. Thus, you will need a separate class for each type of collection in your data contracts:

namespace KjellSJ.Sample.Application.DataContracts
{
[CollectionDataContract(Namespace = "http://kjellsj.blogspot.com/2006/11/DocumentCard", Name = "DocumentCardCollection", ItemName = "DocumentCard")]
public class DocumentCardCollection : Collection<DocumentCard>{}
}


The use of the custom named collection is straightforward:

[System.Runtime.Serialization.DataMemberAttribute(IsRequired = true, Name = "DocumentCardList", Order = 3)]
private DocumentCardCollection _fieldDocumentCardList;

public DocumentCardCollection DocumentCardList
{
get { return _fieldDocumentCardList; }
}


The number of XSDs in your WSDL will increase significantly when applying custom names to collection. I recommend that you group related DataContract classes into a single namespace, e.g. the contract classes DocumentCategory, DocumentCard and DocumentCardCollection should have the same namespace. All contracts in a specific namespace must be versioned as one, so group your contracts wisely.

If you have code that uses the MetadataExchangeClient object to do MEX stuff, you must ensure that the MaximumResolvedReferences setting has room for all the new metadata. I use the MetaData Explorer from IDesign to inspect the service contract, and had to increase the resolve limit beyond the default 10.

1 comment:

Unknown said...

Thanks for your Article..So it means that if the client is .net 2.0 and we are returning the collection from WCF it will get it as array of the object instead of the collection itself..