Creating and Maintaining Service and Data Contracts

Robert Green

MCW Technologies

Download

Articles in this series

Introduction

You saw in an earlier tutorial that service-oriented applications are loosely coupled. All communication occurs through messages. Contracts define the contents of messages. A service will publish a contract that specifies what methods clients can call, what arguments those methods take and what, if anything, the methods return. The contract can also specify security requirements and whether a transaction is required.

In this tutorial, you will review the role of service and data contracts in Windows Communication Foundation. You will also see some best practices you can adopt when designing contracts. Finally, you will explore the consequences of making changes to contracts.

Review the Sample Application

In Visual Studio 2008, select File | Open | Project/Solution to display the Open Project dialog box. Navigate to the folder where you downloaded this tutorial’s sample project. Select ContractsDemo.sln and click OK to open the project. The sample application includes three projects. The ProductRatingServiceLibrary project represents a WCF service that clients can call to retrieve customer ratings for products. The WebHost project uses the ASP.NET Development Server to host the service. The WindowsClient project contains the user interface.

Press F5 to run the application. The Product Ratings form should display the current product ratings (see Figure 1).

Figure 1. Use this form to retrieve and save product ratings.

Click Get rating to retrieve the most recent product rating. Click Clear to clear the textboxes. Enter today’s date in the Date text box, Pavlova in the Product text box, 9 in the Score text box and Strong likes across the board in the Notes text box. Click Save rating to save this new rating. You should then see this rating in the grid (see Figure 2). Close the form.

 

figure02.jpg

Figure 2. You have added a new product rating.

Creating Service Contracts

The first step you will take when creating a WCF service is to create the contract, starting with the service contract. This specifies the operations clients can call, the arguments clients can or must pass for each operation and the values each operation will return. You can use a class to do this, but you should use an interface and separate the definition of the contract from its implementation.

To mark the interface as a service contract, you use the ServiceContract and OperationContract attributes. Open the IProductRatingService file in the sample application and you will see the following:

[ServiceContract]

public interface IProductRatingService

{

  [OperationContract]

  List<ProductRating> GetRatings();

  [OperationContract]

  ProductRating GetRating();

  [OperationContract]

  void AddRating(ProductRating productRating);

}

<ServiceContract()> _

Public Interface IProductRatingService

  <OperationContract()> _

  Function GetRatings() As List(Of ProductRating)

  <OperationContract()> _

  Function GetRating() As ProductRating

  <OperationContract()> _

  Sub AddRating(ByVal _productRating As ProductRating)

End Interface

The WCF service has three operations. When you opened the form, you called GetRatings to retrieve the ratings for all products. This method returns a list of instances of the ProductRating class, which has properties for the date of the rating, the product, the score it received and notes. When you clicked Get rating in the form, you called GetRating to retrieve the rating for a particular product. When you clicked Save rating in the form, you called AddRating to supply a new product rating.

The ServiceContract and OperationContract attributes each have several optional properties you can set to modify the service contract and change how clients and the service communicate. Table 1 lists the properties of the ServiceContract attribute.

Property Description

CallbackContract

Gets or sets the type of the callback contract when the contract is a duplex contract. In a duplex communication, both the client and service can send messages independently. A duplex contract is composed of two one-way contracts, one of which is the callback contract. You can learn more about this in the Messaging Patterns tutorial later in this series.

ConfigurationName

Gets or sets the name used to locate the service configuration in an application configuration file.

HasProtectionLevel

Gets a value that indicates whether the member has a protection level assigned. This enables you to apply security to messages.

Name

Gets or sets the name of the contract.

Namespace

Gets or sets the namespace for messages.

ProtectionLevel

Specifies whether the binding for the contract must support the value of the ProtectionLevel property.

SessionMode

Gets or sets whether sessions are allowed, not allowed or required.

Table 1. You can set these properties of the ServiceContract attribute.

Table 2 lists the properties of the OperationContract attribute.

Property Description

Action

Gets or sets the WS-Addressing action of the request message.

ASynchPattern

Indicates that an operation is implemented asynchronously using a Begin<methodName> and End<methodName> method pair in a service contract.

HasProtectionLevel

Gets a value that indicates whether the messages for this operation must be encrypted, signed, or both.

IsInitiating

Gets or sets a value that indicates whether the method implements an operation that can initiate a session on the server.

IsOneWay

Gets or sets a value that indicates whether an operation returns a reply message.

IsTerminating

Gets or sets a value that indicates whether the service operation causes the server to close the session after the reply message, if any, is sent.

Name

Gets or sets the name of the operation.

ProtectionLevel

Gets or sets a value that specifies whether the messages of an operation must be encrypted, signed, or both.

ReplyAction

Gets or sets the value of the SOAP action for the reply message of the operation.

Table 2. You can set these properties of the OperationContract attribute.

This tutorial only shows the Name and Namespace properties. Future tutorials in this series will cover additional properties of these attributes.

Change the Service Contract’s Namespace

By default, your service contract uses a namespace of http://tempuri.org. To see this, right-click on ProductRatingService.svc in the WebHost project and select View In Browser. You should see the service’s test page in the browser. Select the ProductRatingService.svc?wsdl link to see the service’s WSDL, or Web Service Definition Language. Scroll down to the wsdl:types section to see the namespace (see Figure 3). Close the browser.

 

figure03.jpg

Figure 3. The default namespace of the service is http://tempuri.org.

It is recommended that you provide a namespace for a service contract and that the namespace be a URI that is representative of your company or application domain and that it contain a year and month to support versioning. Open the IProductRatingService file and make the following change to set the Namespace property of the ServiceContract attribute:

[ServiceContract(Namespace = "http://www.mcwtech.com/wcf/2009/05")]

public interface IProductRatingService

<ServiceContract( _

  Namespace:="http://www.mcwtech.com/wcf/2009/05")> _

Public Interface IProductRatingService

Save your changes. Right-click on ProductRatingService.svc in the WebHost project and select View In Browser. Select the ProductRatingService.svc?wsdl link in the browser. Scroll down to the wsdl:types section to see the namespace you specified (see Figure 4). Close the browser.

 

figure04.jpg

Figure 4. You have specified a different namespace for the service.

Press F5 to run the application. The form calls the GetRatings method when it loads. This causes an exception and Visual Studio displays the Exception Assistant (see Figure 5).

 

figure05.jpg

Figure 5. Calling the GetRatings method causes this exception.

Select View Detail to display the View Detail dialog box. Click the + sign to the left of System.ServiceModel.Security.MessageSecurityException. Notice that the InnerException contains the following text:

"The message could not be processed because the action 'http://tempuri.org/IProductRatingService/GetRatings' is invalid or unrecognized."

Click OK to dismiss the View Detail dialog box and select Debug | Stop Debugging to stop the application.

When you add a service reference to a project, Visual Studio queries the service for metadata describing how to communicate with it. The service returns WSDL, which Visual Studio uses to create the proxy class. To see the WSDL, select the WindowsClient project in the Solution Explorer and click the Show All Files button. In the Service References node, expand ProductRatingService. You will see ProductRatingService.wsdl. If you look in that file, you will see the service’s namespace is http://tempuri.org.

This is what the client uses to communicate with the service, but the service is no longer using that namespace. You broke the contract and the client can’t communicate with the service anymore. To fix this, right-click on ProductRatingService and select Update Service Reference. Visual Studio downloads the updated WSDL. Run the application and confirm that it now works.

Many, but not all, changes you make to contracts will immediately break existing clients. You will explore this in more depth later in the tutorial.

Change the Service Contract’s Name

The service’s name defaults to the name of the class that implements it, in this case ProductRatingService. The name of each operation defaults to the name of the method you create. You can use the Name property of the ServiceContract and OperationContract attributes to provide external names that are different from internal names. This enables you to use descriptive names in your code but not expose those names to client applications. It also enables you to supply more user-friendly names to clients without having to change your code.

Return to the IProductRatingService file and make the following changes to set the Name properties of the ServiceContract and OperationContract attributes:

[ServiceContract(Namespace = "http://www.mcwtech.com/wcf/2009/05",

  Name="Ratings")]

public interface IProductRatingService

{

  [OperationContract(Name="GetAllRatings")]

  List<ProductRating> GetRatings();

  [OperationContract(Name="GetLastRating")]

  ProductRating GetRating();

  [OperationContract(Name="AddNewRating")]

  void AddRating(ProductRating productRating);

}

<ServiceContract( _

  Namespace:="http://www.mcwtech.com/wcf/2009/05", _

  Name:="Ratings")> _

Public Interface IProductRatingService

  <OperationContract(Name:="GetAllRatings")> _

  Function GetRatings() As List(Of ProductRating)

  <OperationContract(Name:="GetLastRating")> _

  Function GetRating() As ProductRating

  <OperationContract(Name:="AddNewRating")> _

  Sub AddRating(ByVal _productRating As ProductRating)

End Interface

Save your changes and build the solution. You changed the service contract, so right-click on ProductRatingService in the WindowsClient project and select Update Service Reference. Right-click on Form1 and select View Code. Notice a squiggly line under the following code:

proxy = new ProductRatingServiceClient(

  "WSHttpBinding_IProductRatingService");

Private proxy As ProductRatingServiceClient = Nothing

proxy = New ProductRatingServiceClient( _

  "WSHttpBinding_IProductRatingService")

Visual Studio bases the name of the proxy class on the public name of the service. You changed that so Visual Studio changed the name of the proxy class. It is now RatingsClient. Make the following change to the proxy class declaration:

private RatingsClient proxy = null;

Private proxy As RatingsClient = Nothing

Make the following change to the Form1_Load method:

proxy = new RatingsClient("WSHttpBinding_Ratings");

proxy = New RatingsClient("WSHttpBinding_Ratings")

Notice that Visual Studio changed the name of the endpoint to match the new public name of the service.

Make the following change to the Form1_Load method:

productRatings = proxy.GetAllRatings();

productRatings = proxy.GetAllRatings()

Make the following change to the getRatingbutton_Click method:

productRating = proxy.GetLastRating();

_productRating = proxy.GetLastRating()

Make the following changes to the saveRatingButton_Click method:

proxy.AddNewRating(productRating);

ClearTextBoxes();

productRatings = proxy.GetAllRatings();

proxy.AddNewRating(_productRating)

ClearTextBoxes()

productRatings = proxy.GetAllRatings()

Save your changes and rebuild the solution. Run the application and confirm it works as expected.

Creating Data Contracts

The service contract describes what the service can do. The data contract describes the data that clients and the service will exchange. Clients and services exchange data via XML. When the client calls a service operation that takes arguments, the WCF runtime converts .NET types to XML. When the service receives the operation call, the runtime converts the XML to the appropriate .NET types. If the method returns a value, the same process occurs.

The WCF runtime uses the Data Contract Serializer to serialize (convert to XML) and deserialize data (convert from XML). This serializer automatically works with primitive .NET types, such as string and integers, and types such as DateTime and TimeSpan.

However, the serializer does not automatically work with more complex types, such as arrays, enumerations and classes. To make these types serializable, you define a data contract. To mark a type as a data contract, you use the DataContract and DataMember attributes. The IProductRatingService file contains the following code to enable clients and the service to exchange instances of the ProductRating class:

[DataContract]

public class ProductRating

{

  [DataMember]

  public DateTime DateSubmitted { get; set; }

  [DataMember]

  public string ProductName { get; set; }

  [DataMember]

  public int AverageScore { get; set; }

  [DataMember]

  public string Notes { get; set; }

}

<DataContract()> _

Public Class ProductRating

  <DataMember()> _

  Public Property DateSubmitted() As DateTime

  <DataMember()> _

  Public Property ProductName() As String

  <DataMember()> _

  Public Property AverageScore() As Integer

  <DataMember()> _

  Public Property Notes() As String

End Class

Note that the Visual Basic code listing above is abbreviated to only show the DataContract and DataMember attributes.

The DataContract and DataMember attributes each have several optional properties you can set to modify the data contract and change how clients and the service communicate. The DataContract attribute has Name and Namespace properties. Table 3 lists the properties of the DataMember attribute.

Property Description

EmitDefaultValue

Gets or sets a value that specifies whether to serialize the default value for a field or property being serialized.

IsRequired

Gets or sets a value that instructs the serialization engine that the member must be present when reading or deserializing.

Name

Gets or sets the name of the member.

Order

Gets or sets the order of serialization and deserialization of a member.

Table 3. You can set these properties of the DataMember attribute.

Change the Data Contract’s Properties

It is recommended that you provide a namespace for a data contract, just as you did above for the service contract. The data contract namespace should be similar to the service contract namespace, perhaps including a schemas prefix. You can also use the Name property to provide an external name for the ProductRating class.

Open the IProductRatingService file and make the following changes to set the Namespace and Name properties of the DataContract attribute:

[DataContract(Namespace =

  "http://www.mcwtech.com/wcf/schemas/2009/05", Name="Rating")]

  public class ProductRating

<DataContract( _

  Namespace:="http://www.mcwtech.com/wcf/schemas/2009/05", _

  Name:="Rating")> _

Public Class ProductRating

You can use the Name property of the DataMember attribute to provide external names for members of the ProductRating class. You can also use the Order property to control what order members appear in the schema. Make the following changes to set these properties of the DataMember attributes:

[DataMember(Name="Submitted", Order=2)]

public DateTime DateSubmitted { get; set; }

[DataMember(Name="Product", Order=1)]

public string ProductName { get; set; }

[DataMember(Name="Score", Order=3)]

public int AverageScore { get; set; }

[DataMember(Name="Details", Order=4)]

public string Notes { get; set; }

<DataMember(Name:="Submitted", Order:=2)> _

Public Property DateSubmitted() As DateTime

<DataMember(Name:="Product", Order:=1)> _

Public Property ProductName() As String

<DataMember(Name:="Score", Order:=3)> _

Public Property AverageScore() As Integer

<DataMember(Name:="Details", Order:=4)> _

Public Property Notes() As String

Save your changes and build the solution. You changed the data contract, so right-click on ProductRatingService in the WindowsClient project and select Update Service Reference. Right-click on Form1 and select View Code. If you are using C#, build the solution again. If you are using Visual Basic, there is no need to take this step thanks to the background compiler. Notice squiggly lines under the following code:

private ProductRating productRating = null;

private List<ProductRating> productRatings = null;

Private _productRating As ProductRating = Nothing

Private productRatings As List(Of ProductRating) = Nothing

There is a ProductRating class in the service’s code, but when Visual Studio queried the service for metadata, it found a Rating class. You need to change your code to use that. Make the following changes to the declarations above:

private Rating productRating = null;

private List<Rating> productRatings = null;

Private _productRating As Rating = Nothing

Private productRatings As List(Of Rating) = Nothing

Notice squiggly lines under the following code in the getRatingButton_Click method:

dateMaskedTextBox.Text =

  productRating.DateSubmitted.ToString("MM/dd/yyyy");

productTextBox.Text = productRating.ProductName;

scoreTextBox.Text = productRating.AverageScore.ToString();

notesTextBox.Text = productRating.Notes;

dateMaskedTextBox.Text = _

  _productRating.DateSubmitted.ToString("MM/dd/yyyy")

productTextBox.Text = _productRating.ProductName

scoreTextBox.Text = _productRating.AverageScore.ToString()

notesTextBox.Text = _productRating.Notes

The ProductRating class in the service’s code has the field names above, but the Rating class in the client code uses the names you specified when you added the Name property to the DataMember attributes. Make the following changes to the declarations above:

dateMaskedTextBox.Text =

  productRating.Submitted.ToString("MM/dd/yyyy");

productTextBox.Text = productRating.Product;

scoreTextBox.Text = productRating.Score.ToString();

notesTextBox.Text = productRating.Details;

dateMaskedTextBox.Text = _

  _productRating.Submitted.ToString("MM/dd/yyyy")

productTextBox.Text = _productRating.Product

scoreTextBox.Text = _productRating.Score.ToString()

notesTextBox.Text = _productRating.Details

Make the following changes to the saveRatingButton_Clickmethod:

productRating = new Rating();

productRating.Submitted =

  Convert.ToDateTime(dateMaskedTextBox.Text);

productRating.Product = productTextBox.Text;

productRating.Score = Convert.ToInt32(scoreTextBox.Text);

productRating.Details = notesTextBox.Text;

_productRating = New Rating()

_productRating.Submitted = _

  Convert.ToDateTime(dateMaskedTextBox.Text)

_productRating.Product = productTextBox.Text

_productRating.Score = Convert.ToInt32(scoreTextBox.Text)

_productRating.Details = notesTextBox.Text

Save your changes and rebuild the solution. Run the application and confirm it works as expected. Notice that the columns in the grid are now in the order you specified via the Order property of the DataMember attribute (see Figure 6).

 

figure06.jpg

Figure 6. The columns in the grid now appear in the order you specified.

Versioning Service and Data Contracts

The changes you just made to the service and data contracts impact existing client applications. You should make these types of changes before you deploy the service. Before the service goes into production, you should settle on the names used by clients.

However, it is likely that over time you will have a need to make additional changes to the service’s contracts. WCF service and data contracts have a degree of version tolerance. Not every change you make will immediately break existing clients. Table 4 lists several changes you might make to a service contract and their impact on existing clients.

Service Contract Change Impact on Existing Clients

Change a method’s parameter type

Clients may break when they call the method.

Add a parameter to a method

Clients can call the method and not supply the parameter. The service sets the value of the parameter to its default value.

Remove a parameter from a method

Clients can call the method and supply the parameter. The service does not use the supplied data.

Change a method’s return type

Clients may break when they call the method.

Add a method

There is no impact on clients.

Remove a method

Clients may break when they call the method

Table 4. This lists several changes you might make to a service contract and their impact on existing clients.

Table 5 lists several changes you might make to a data contract and their impact on existing clients.

Data Contract Change Impact on Existing Clients

Add an optional member

Clients can continue to use the version of the class without the member. The service sets the value of the member to its default value.

Remove an optional member

Clients can continue to use the version of the class with the member. The service does not use the supplied data.

Add a required member

Clients will break if they use the version of the class without the member.

Remove a required member

Clients will break if they use the version of the class with the member.

Modify a member’s type

Clients may break when they use the version of the class with the original type.

Table 5. This lists several changes you might make to a data contract and their impact on existing clients.

You will next make some of these changes to the service and see what impact those changes have on existing clients.

Add a method

Suppose that after using the application for a while, users ask for the ability to see the average score across all ratings. To accommodate this request, return to the IProductRatingService file and add the following to the interface:

[OperationContract(Name="GetAverageScore")]

int CalcAverageScore();

<OperationContract(Name:="GetAverageScore")> _

Function CalcAverageScore() As Integer

In the Solution Explorer, double-click the ProductRatingService file and add the following method to implement the addition to the interface:

public int CalcAverageScore()

{

  var avgScore = (from rating in productRatings

    select rating.AverageScore).Average();

  return Convert.ToInt32(avgScore);

}

Public Function CalcAverageScore() As Integer _

  Implements IProductRatingService.CalcAverageScore

  Dim avgScore = _

    (From rating In productRatings _

    Select rating.AverageScore).Average

  Return Convert.ToInt32(avgScore)

End Function

Save your changes and build the solution. To update the client application’s proxy class right-click on ProductRatingService in the WindowsClient project and select Update Service Reference. Double-click on Form1 to open the form in the Form Designer.

From the Toolbox, drag a Button control to the form. Make it the same size as the other buttons. Name the button getAverageButton. Set the button's text property to "Get average". Double-click on the button to create the button’s Click event handler. Add the following code to the getAverageButton_Click method:

MessageBox.Show(string.Format(

  "The average score is {0}", proxy.GetAverageScore()));

MessageBox.Show(String.Format( _

  "The average score is {0}", proxy.GetAverageScore()))

Save your changes and build the solution. Press F5 to run the application. In the Product Ratings form, click Get average to retrieve the average rating score (see Figure 7). Click OK to dismiss the average score message. Close the form.

 

figure07.jpg

Figure 7. Click Get average to see the average rating score.

You have modified the service contract, but no existing applications will break. To get the added functionality in a client application, you can simply update the service reference and regenerate the proxy class, which will now have a GetAverageScore method.

Change a method’s return type

After a time, the users of the application may decide they want to see average scores in more detail. Specifically, they may ask to see scores include a decimal place. To accommodate this request, return to the IProductRatingService file and make the following change:

[OperationContract(Name="GetAverageScore")]

double CalcAverageScore();

<OperationContract(Name:="GetAverageScore")> _

Function CalcAverageScore() As Double

In the the ProductRatingService file, make the following changes to the CalcAverageScore method:

public double CalcAverageScore()

{

  var avgScore = (from rating in productRatings

    select rating.AverageScore).Average();

  return Convert.ToDouble(avgScore);

}

Public Function CalcAverageScore() As Double _

  Implements IProductRatingService.CalcAverageScore

  Dim avgScore = _

    (From rating In productRatings _

    Select rating.AverageScore).Average

  Return Convert.ToDouble(avgScore)

End Function

Save your changes and build the solution. Press F5 to run the application. In the Product Ratings form, click Get average to retrieve the average rating score. This causes an exception and Visual Studio displays the Exception Assistant (see Figure 8). Select Debug | Stop Debugging to stop the application.

 

figure08.jpg

Figure 8. Clicking the Get average button on the form causes this exception.

The service contract specifies that the GetAverageScore operation returns an integer, but it now returns a double. The .NET Framework runtime is capable of converting a double to an integer, but the WCF runtime is not so forgiving. It respects the contract and throws this exception.

This is one of the key takeaways in this tutorial. The change you made seems relatively benign, and your initial thought may be that it is not a breaking change, just a potential data loss change. However, this change broke the existing client application.

To fix the client application, right-click on ProductRatingService in the WindowsClient project and select Update Service Reference. In the Form1 code file, make the following change to the getAverageButton_Click method to display the average score with one decimal place:

MessageBox.Show(string.Format(

  "The average score is {0:F1}", proxy.GetAverageScore()));

MessageBox.Show(String.Format( _

  "The average score is {0:F1}", proxy.GetAverageScore()))

Save your changes and build the solution. Press F5 to run the application. In the Product Ratings form, click Get average to retrieve the average rating score. Click OK to dismiss the average score message. Close the form.

Add a required member

Suppose you now decide that you want to keep track of who submitted product ratings. To do this, you will add a member to the ProductRating class and make that member required. In the IProductRatingService file, add the following code to the ProductRating class:

[DataMember(Name="Author", IsRequired=true, Order=5)]

public string SubmittedBy { get; set; }

Private submittedByValue As String

<DataMember(Name:="Author", IsRequired:=True, Order:=5)> _

Public Property SubmittedBy() As String

  Get

    Return submittedByValue

  End Get

  Set(ByVal value As String)

    submittedByValue = value

  End Set

End Property

Save your changes and build the solution. Press F5 to run the application. Enter today’s date in the Date text box, Pavlova in the Product text box, 9 in the Score text box and Strong likes across the board in the Notes text box. Click Save rating to save this new rating. This causes an exception and Visual Studio displays the Exception Assistant (see Figure 9). Select Debug | Stop Debugging to stop the application.

 

figure09.jpg

Figure 9. Clicking the Save rating button on the form causes this exception.

Select View Detail to display the View Detail dialog box. Click the + sign to the left of System.ServiceModel.Security.MessageSecurityException. Notice that the Message property contains the following text:

"The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://www.mcwtech.com/wcf/2009/05:productRating. The InnerException message was 'Error in line 1 position 562. 'EndElement' 'productRating' from namespace 'http://www.mcwtech.com/wcf/2009/05' is not expected. Expecting element 'Author'.'.  Please see InnerException for more details."

Click OK to dismiss the View Detail dialog box and select Debug | Stop Debugging to stop the application.

The data contract specifies that the client supply an Author element in the XML it sends. The ProductRating class the client is using does not have an Author member and will not until you update the service reference.

To fix the client application, right-click on ProductRatingService in the WindowsClient project and select Update Service Reference. In the Form1 code file, add the following code to the saveRatingButton_Click method:

productRating.Details = notesTextBox.Text;

productRating.Author = "Robert";

proxy.AddNewRating(productRating);

_productRating.Details = notesTextBox.Text

_productRating.Author = "Robert"

proxy.AddNewRating(_productRating)

Save your changes and build the solution. Press F5 to run the application. In the form, enter today’s date in the Date text box, Pavlova in the Product text box, 9 in the Score text box and Strong likes across the board in the Notes text box. Click Save rating to save this new rating. The new rating should appear with the author information (see Figure 10).

 

figure10.jpg

Figure 10. New ratings now include the name of the submitter.

Choose a Versioning Strategy

WCF services are somewhat version tolerant by default. You can make changes to a service that won’t immediately break client applications. Suppose you remove the IsRequired property of the SubmittedBy member, or set it to false. The member is now optional. You would not have to change the client code. Clients would set the Author property of the ProductRating object and send it to the service. The service would use the data if the client sends it, but no exception occurs if it is not sent.

However, as soon as a developer updates the service reference, the client application will break. Visual Studio will redefine the ProductRating class, which will no longer have an Author property.

Version tolerance refers to what happens if the host and client are not using the same service and data contracts. As soon as you update the service reference, you have to make sure your client code doesn’t violate either contract.

Before you publish your WCF services, you should consider how you are going to handle versioning. In other words, how are you going to handle changes you may make to your service? You might consider the following three strategies: agile versioning, strict versioning and semi-strict versioning.

Agile versioning

In the agile versioning strategy, you rely on version tolerance as much as possible. If you know that a change will not break existing clients, you publish it in the interests of getting changes out as soon as possible. Eventually, someone will have to update client applications, but their owners can choose when they do that.

This strategy provides fast turnaround at the expense of client application fragility.

Strict versioning

In this strategy, any changes require a new contract with a new version and a new endpoint. When you added the CalcAverageScore method above, you would have created a new interface, IProductRatingService2, with the new method. You would have then created a ProductRatingService2.svc file. Clients that want to use the new method would add a new service reference using https://localhost:8080/ProductRatingService2.svc as the service’s address.

This strategy ensures no client application breaks, but requires a new service reference and client changes every time you make a change to the service.

Semi-strict versioning

This strategy combines the agile and strict versioning strategies. For example, you might rely on agile versioning for changes that do not immediately break existing applications. As soon as you make a change that will break applications, you create a new version of the service and roll up all previous changes.

The paper Essential Windows Communication Foundation discusses these strategies in more detail.

Conclusion

In this tutorial, you reviewed creating and maintaining service and data contracts. The service contract describes what the service can do. What operations can clients call? What parameters must they pass? What values do the operations returns? The data contract describes the data that clients and the service will exchange. You do not need a data contract if you use primitive .NET types such as strings or integers. You do need to create a data contract if you use more complex types such as arrays or classes.

You also saw that WCF has some version tolerance built-in. Some changes you might make to a service or data contract will immediately break client applications. Other changes will not immediately break client applications. Just be aware that as soon as you update the service reference in the client, you need to make sure the client code does not conflict with the new contracts.

Finally, you briefly reviewed versioning strategies. You should decide how you are going to manage changes, both at the service and at clients. Your strategy can vary across services, but you do need a strategy for each service.

About the Author

Robert Green is a developer, writer, and trainer. He is a senior consultant with MCW Technologies. Robert is a Visual Studio Tools for the Office system MVP and is a co-author of AppDev courseware for Microsoft Visual Basic, Microsoft Visual C#, LINQ and Microsoft Windows Workflow Foundation. Before joining MCW, Robert worked at Microsoft as a Product Manager and also a Program Manager.