This monthly column explores how to build Web services with .NET 3.0 and WCF. This two-part installment covers how to version your service contracts and data contracts. If you have questions about migrating your existing ASMX or WSE Web services to WCF, or questions regarding WCF, please send them to editors@devproconnections.com!

Part I of this article series introduced the subject of contract versioning. I discussed the importance of contracts and policy; noted how WCF supports version tolerance; provided a summary of versioning strategies for service contracts and data contracts; and explained in detail how to version service contracts. Part II continues the discussion with versioning strategies for data contracts.

Brief Review

Before I jump in to the details of data contract versioning, let me provide a quick summary of what I discussed in Part I to make sure we re on the same page. Here are some points you should be clear on:

  • Service contracts and data contracts drive the shape of the WSDL (Web Service Description Language) contract that is used by clients to consume a service. The WSDL contract defines a set of operations that are available at a particular address, and the schema for the messages that can be sent to, or returned from, those operations. This includes schema for any complex types (data contracts) upon which you might be relying.
  • Policy is something that can be included in the WSDL contract to define other service behaviors, such as message encoding formats (like MTOM), security, reliable messaging, transactions, and other related features. Policy also affects messaging, but is not relevant to this article s discussion of service contract and data contract versioning.
  • Both WSDL and related policy should not change once published if you want to support backward compatibility with existing clients. If either changes without client knowledge, they will no longer be able to communicate with the service.
  • By default, WCF service contracts and data contracts are version tolerant.
  • With service contracts you may add operations and modify parameter or return types without impact to clients, but you should not remove operations.
  • With data contracts you can add new non-required members, remove non-required members, and modify member data types but you should not remove or add required members if you want to preserve backward compatibility.
  • Data contracts also have a feature that allows you to preserve unknown members during deserialization (using IExtensibleDataObject).

Although contracts are version tolerant, you still must consider a versioning policy that suits your development process. Part I covered several approaches to versioning service contracts based on adopting a strict or non-strict policy. Really, the choice of policy drives the threshold before which you perform formal contract versioning so at some point, you must consider the question, How do I version a contract? I explained how to do this with service contracts; now I ll discuss data contracts.

Data Contract Versioning Policies

Data contract versioning implies that the schema definition for the CLR type has been altered in some way which can also be triggered by changes to a nested data member. As with service contracts, you can adopt a non-strict or strict set of versioning guidelines when changes occur.

Non-strict Guidelines. In many cases, you can defer formal versioning of data contracts and avoid the associated development overhead. Basically, this refers to adding or removing non-required members at any level of the data contract hierarchy. Figure 1 illustrates the effect of these version-tolerant changes. As noted in Part I, when you remove non-required members, clients with an earlier version of the data contract will initialize missing members to defaults. Likewise, if you add new non-required members, services will initialize missing members to defaults. While at the service you can control how you handle those defaults, clients may not be recompiled, thus presenting less-useful information to users. Consider the impact of this and whether or not formal versioning should be used.

 


Figure 1: Version-tolerant changes to data contracts.

 

While version-tolerant changes will support a non-strict approach to versioning, other changes require you to formally version data contracts. Figure 2 illustrates the impact of adding or removing required members from a data contract. When required members are removed, clients compiled against the original data contract will encounter an exception when responses sent by the service are missing those members. When required members are added, existing clients will not send the new member, thus the service will encounter an exception.

 


Figure 2: Data contract changes that impact existing clients.

 

Modifications to data types also represent an unacceptable change to a data contract that should require formal versioning. Figure 3 summarizes non-strict guidelines to versioning data contracts.

 


Figure 3: Non-strict data contract versioning guidelines.

 

Strict Guidelines. With data contracts, you can defer formal versioning until you make changes that will impact existing clients, such as adding or removing required members, or changing data types of members. If you adopt a strict approach to versioning, absolutely any change should result in formal versioning; Figure 4 illustrates this approach.

 


Figure 4: Strict data contract versioning guidelines.

 

As mentioned, when you version a data contract, any service contracts dependent on that data contract must also be formally versioned (as discussed in Part I). That implies a new service contract and service endpoint.

 

Formal Data Contract Versioning

So far, I've covered two options for data contract versioning: adopt a non-strict policy and defer versioning until changes demand it; or, adopt a strict versioning policy, which means any changes demand formal versioning. In either case, formal versioning is inevitable at some point unless you don t intend to be backward compatible to existing clients.

The key driver behind formal versioning is likely to be an evolution of the business logic behind the service. That means that business components, data access components, and the business entities they rely on have probably changed to support new features or necessary modifications to existing features. Consider Figure 5, which illustrates a V1 client consuming a V1 service and associated business logic.

 


Figure 5: Client, service, business, and data tier code for a content management service.

 

The ContentManagerService consumes downstream business and data access components to perform content management. Content in this example is represented by a LinkItem type, which can be a link to an article, a code sample download, an MP3 file, a photo, or any other content. The service contract exposing content management functionality supports operations to save and retrieve LinkItem content, as shown in the following service contract, IContentManagerService:

[ServiceContract(Name = "ContentManagerContract",
Namespace = http://www.thatindigogirl.com/samples/2007/06)]
public interface IContentManagerService
{
  [OperationContract] 
 void SaveItem(LinkItem item); 
  [OperationContract] 
 LinkItem GetItem(string id);

}
 

The initial version of the LinkItem type is shown in Listing One. When the SaveItem operation is called, the ContentManagerService passes a LinkItem instance to downstream components to save it to the database. When GetItem is called, the same downstream components are leveraged to construct a particular LinkItem instance from the database and return it to the client.

In Listing One, the LinkItem business entity is not only used by business and data access components, but is also the data contract for serialization when service operations are called. This is the ideal scenario because it reduces the overhead of mapping from data contract to alternate business entities, and vice versa.

Consider a scenario where the application evolves to support extended types of content that require additional information. For example, to support an event the LinkItem would include a beginning and ending date. This change requires updates to the LinkItem type, the business and data access components, and the database. The service will also change to use the new LinkItem type, and thus the data contract for the LinkItem type will be versioned along with the service contract.

Listing Two shows changes made to the LinkItem type and its data contract. Note that the LinkItem CLR type has changed, but the name remains the same because all components will be updated to use the latest version of this type. That is, all except for the existing service contract.

A new service contract must be created so it can be versioned and exposed over a new endpoint. That way, existing clients can continue to use the original service endpoint. The following code shows the new service contract:

[ServiceContract(Name = "ContentManagerContract",
Namespace = http://www.thatindigogirl.com/samples/2007/08)]
public interface IContentManagerServiceV2
{
  [OperationContract] 
 void SaveItem(LinkItem item); 
  [OperationContract] 
 LinkItem GetItem();
}

Note that the CLR definition has a new suffix ( V2 ), but the Name property is still ContentManagerContract . The Namespace has been updated, however, to reflect the month and year of the change. This differentiates messages on the wire from the earlier contract.

According to the formal service contract versioning rules I discussed in Part I, a new service endpoint (EP2) would also be created for the updated service contract. When new clients generate proxies from the new endpoint, they get the latest service contract and data contracts with associated type metadata, as shown on the left side of Figure 6.

 


Figure 6: V1 and V2 client and service components after versioning.

 

But, what happens to existing clients? To preserve backward compatibility, the original endpoint (EP1) must still be available, with the original service contract and data contracts (see the right side of Figure 6). The problem is that the LinkItem type is no longer the same it has new features, and the data contract has been versioned. If the old contract continues to use the same CLR type, LinkItem, it will modify the original endpoint and break backward compatibility.

A simple way to address this for existing clients is to create a copy of the original LinkItem type before modifications are made, and rename the type to LinkItemV1, as shown here:

[DataContract(Name="LinkItem", Namespace=
http://schemas.thatindigogirl.com/samples/2007/06)]
public class LinkItemV1

Keep the same data contract as before, so that it appears the same on the wire for existing clients, and update the original service contract to use this new type:

[ServiceContract(Name = "ContentManagerContract",
Namespace = http://www.thatindigogirl.com/samples/2007/06)]
public interface IContentManagerService
{
  [OperationContract] 
 void SaveItem(LinkItemV1 item); 
  [OperationContract] 
 LinkItemV1 GetItem();
}

As for configuration, the original service and endpoint remain the same, and still point to the original service and service contract. A new service configuration is created for the new service type and contract, with associated endpoint, as shown in Figure 7.

Figure 7: A new service configuration is created

<services> 
 <service name="ContentManager.ContentManagerService"> 
   <endpoint address="net.tcp://localhost:9000/ContentManagerService"
contract="ContentManager.IContentManagerService" binding="netTcpBinding" /> 
 </service> 
 <service name="ContentManager.ContentManagerServiceV2"> 
   <endpoint address=" net.tcp://localhost:9001/ContentManagerServiceV2"
contract="ContentManager.IContentManagerServiceV2" binding="netTcpBinding" /> 
 </service>
</services>

The only item missing from Figure 6 is how calls from existing clients rely on LinkItemV1, while business and data access components rely on LinkItem. As far as the application functionality is concerned, there s only one type: LinkItem. Business and data access components don t care how you handle serialization at the service tier, so long as they work with a valid LinkItem.

Until existing clients migrate to the new endpoint to take advantage of new features, what you can do is modify the service functionality for existing clients so that it maps LinkItemV1 to LinkItem, and vice versa, then leverages the same business and data access components (as shown in Figure 8).

 


Figure 8: Mapping LinkItemV1 to LinkItem for existing clients.

 

If you follow this guidance, your latest and greatest service endpoints will always work with the latest and greatest business entities, as does the underlying system. By making copies of earlier versions for backward compatibility, you can create a clear separation between the main code base and supporting code to handle legacy clients.

In summary, the steps to version your data contracts might follow this flow:

  • New feature requirements require changes to the system.
  • Make a backup copy of the affected business entities used at the service tier, rename the entities, no changes to data contract, no changes to existing service contracts or endpoints.
  • Modify affected business entities as needed for new functionality, no change to type name, version the data contract.
  • Create a new service contract with an updated namespace reflecting the change, use business entities with the updated data contract.
  • Update the original service contract so it uses the backup copy of the modified business entity with the original data contract.
  • Create a new service configuration section with a new endpoint exposing the updated service contract.
  • Leave the original endpoint intact.

 

Conclusion

In this two-part series I ve discussed WCF version tolerance and versioning policies for service contracts and data contracts, and provided you with the steps for versioning both types of contracts. You should consider this discussion and weigh the pros and cons of adopting a strict or non-strict versioning policy according to the needs of your development lifecycle.

I recommend you adopt a strict versioning policy, where possible, for change tracking and to ensure backward compatibility with less risk. Although it will add some overhead to the development effort at first, it will eventually become a somewhat mechanical process for developers which should help get the team in the groove of doing the right thing.

For examples related to service contracts and data contracts discussed in this article, see sample code for Chapter 2 of my book, Learning WCF (code for the entire book is available from http://www.thatindigogirl.com/).

 

Michele Leroux Bustamante is Chief Architect of IDesign Inc., Microsoft Regional Director for San Diego, Microsoft MVP for Connected Systems, and a BEA Technical Director. At IDesign Michele provides training, mentoring, and high-end architecture consulting services focusing on Web services, scalable and secure architecture design for .NET, federated security scenarios, Web services, interoperability, and globalization architecture. She is a member of the International .NET Speakers Association (INETA), a frequent conference presenter, conference chair for SD West, and is frequently published in several major technology journals. Michele is also on the board of directors for IASA (International Association of Software Architects), and a Program Advisor to UCSD Extension. Her latest book is Learning WCF (O Reilly, 2007); see her book blog at http://www.thatindigogirl.com/. Reach her at mailto:mlb@idesign.net or visit http://www.idesign.net/ and her main blog at http://www.dasblonde.net/.

Begin Listing One Initial LinkItem type with data contract definition

[DataContract(Name="LinkItem", Namespace=
http://schemas.thatindigogirl.com/samples/2007/06)]
public class LinkItem
{
 private long m_id; 
 private string m_title; 
 private string m_description; 
 private DateTime m_itemDate; 
 private string m_url; 
  [DataMember(Name = "Id", IsRequired = false, Order = 0)] 
 public long Id
 { 
    get { return m_id; }
   set { m_id = value; }
 } 
  [DataMember(Name = "Title", IsRequired = true, Order = 1)] 
 public string Title
 { 
   get { return m_title; }
   set { m_title = value; }
 } 
  [DataMember(Name = "Description", IsRequired =
   false, Order = 2)] 
 public string Description
 { 
   get { return m_description; }
   set { m_description = value; }
 } 
  [DataMember(Name = "Url", IsRequired = true, Order = 3)] 
 public string Url
 { 
   get { return m_url; }
   set { m_url = value; }
 } 
  [DataMember(Name = "ItemDate", IsRequired = true,
   Order = 4)] 
 public DateTime ItemDate
 { 
   get { return m_itemDate; }
   set { m_itemDate = value; }
 }
}

 

End Listing One

 

Begin Listing Two Updated LinkItem type with data contract V2

[DataContract(Name = "LinkItem", Namespace =
 http://schemas.thatindigogirl.com/samples/2007/08)]  

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; 
 // other property accessors
  [DataMember(Name = "DateStart", IsRequired = true,
   Order = 4)] 
 public DateTime DateStart
 { 
   get { return m_dateStart; }
   set { m_dateStart = value; }
 } 
  [DataMember(Name = "DateEnd", IsRequired = false,
   Order = 5)] 
 public DateTime DateEnd
 { 
   get { return m_dateEnd; }
   set { m_dateEnd = value; }
 }
}

 

End Listing Two