Friday, November 17, 2006

WCF: Designing a Hierarchical Data Contract

We have now started designing the data contracts of our WCF services using the WSSF (Web Service Software Factory). All samples and demos I have seen so far shows simple entity objects such as a customer with a few basic value type fields. In this post I will show how to design a data contract for hierarchical data that uses value types, enums, entity objects and collections of entities.

The WSSF wizards require you to design the contracts bottom-up, as any object must exists before it can be added to the contract. Note also that a wizard cannot be rerun to expand data contracts, it can only create new contracts. This limitation is espesiaclly daft when it comes to service interfaces: you cannot add another operation to an existing service contract using the wizard. Thus, you must start by defining the leaf nodes of the structure and work your way up to the root element of the hierarchy.

I still recommend that you do a little design up front: start by identifying and writing down the use cases, identityfy the services (logical groups of operations) that you need to support the use cases, and finally write down a list of the logical operations that you think are sufficient to implement the business processes of the use cases. Use high level terms (verbs and nouns) to outline the set of operations (list, get, register, fulfill, etc; customer, offer, order, invoice, etc). Ensure that the operations in fact are business services, and not a RPC/CRUDy style programming model exposed as web-services.

The data in this example is a list of documents organized into a categorizing hierarchy. Each document again consists of metadata (the document card) and a list of files making up the document (classical DM stuff). Each file has to have an associated file type (enum).

I now prefer using the contract attributes over creating XSD schemas first, even if I still am a big fan of the contract first approach. I always extract the XSD schemas and inspect them with XmlSpy to ensure that the generated schemas looks the way I would like them to be.

This is how the data contract schema should look (split into two for readability, click to enlarge):


The file type is an XSD enum:
The data contract wizard makes it easy to add value types, enums and objects to the contract. It is not, however, possible to add a System.Collections.ObjectModel Collection<T> using the wizard. To add a collection, first add just the type (T) to the contract, then generate the contract class and modify it like this:

using System;
using System.Runtime.Serialization;
using System.Collections.ObjectModel;

namespace KjellSJ.Sample.Application.DataContracts
{

/// <summary>
/// Data Contract Class - ProjectDocuments
/// </summary>

[System.Runtime.Serialization.DataContractAttribute(Namespace = "http://kjellsj.blogspot.com/2006/11/ProjectDocuments", Name = "ProjectDocuments")]
public partial class ProjectDocuments
{
public ProjectDocuments()
{
_fieldChildNodes = new Collection<ProjectDocuments>();
_fieldDocumentCardList = new Collection<DocumentCard>();
}

private System.String _fieldId;

[System.Runtime.Serialization.DataMemberAttribute(IsRequired = true, Name = "Id", Order = 1)]
public System.String Id
{
get { return _fieldId; }
set { _fieldId = value; }
}

private System.String _fieldCode;

[System.Runtime.Serialization.DataMemberAttribute(IsRequired = true, Name = "Code", Order = 2)]
public System.String Code
{
get { return _fieldCode; }
set { _fieldCode = value; }
}

private System.String _fieldDescription;

[System.Runtime.Serialization.DataMemberAttribute(IsRequired = false, Name = "Description", Order = 3)]
public System.String Description
{
get { return _fieldDescription; }
set { _fieldDescription = value; }
}

[System.Runtime.Serialization.DataMemberAttribute(IsRequired = false, Name = "ChildNodes", Order = 4)]
private Collection<ProjectDocuments> _fieldChildNodes;

public Collection<ProjectDocuments> ChildNodes
{
get { return _fieldChildNodes; }
}

[System.Runtime.Serialization.DataMemberAttribute(IsRequired = false, Name = "DocumentCardList", Order = 5)]
private Collection<DocumentCard> _fieldDocumentCardList;

public Collection<DocumentCard> DocumentCardList
{
get { return _fieldDocumentCardList; }
}

}
}

As you can see, I have made the DocumentCardList a Collection<DocumentCard> and moved the DataMember attribute to the private member instead of the public property. The reason for moving the attribute is that I made the collection property read-only, a best practice, and the data contract serializer does not support read-only properties.

Note also the self reference to ProjectDocuments in the ChildNodes collection that makes this schema a hierachy. The collection becomes a XSD array:

<xs:complexType name="ArrayOfProjectDocuments">
<xs:sequence>
<xs:element name="ProjectDocuments" type="tns:ProjectDocuments" nillable="true" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>

Note that to adhere to the WS-I basic profile best practices, you should customize the collection element name using the [CollectionDataContract] attribute. Read more about using collections in the data contract here at MSDN.

I prefer not to mark as DataContract any enum that is referenced by a DataMember in the schemas. If you apply DataContracty to the enum, it will cause the fields using the enum to be interpreted a XSD string data type instead of a enum data type (xs:enumeration). Just leave the enum class as is and use it as any other datatype in the DataContract wizard. Read more about using enums in data contracts here at MSDN.

2 comments:

INDIAN said...

Thank you, for a different view of the problem - yes, google full of simple datastructures they copy, and they claim themselves - MVPs :))

thanks again for the article

INDIAN said...

Is there any chance to manage namespace declarations, one as default, which node should get which namespace to...