in

Foo Theory

Partners in Community - serving up some ice cold Kool-Aid!
Welcome to footheory.com.  The bloggers and contributing members on this site are consultants, project/program managers and software architects working across the US.  Our community will focus on Microsoft technologies, .NET architecture, software patterns & practices and just plain stream of consciousness.

Bennie's Weblog

Handling Data Contract Object Hierarchies in WCF

In this post, we will illustrate some lesser-known features of WCF, which will sooner or later become valuable tools in your WCF toolbox. The topics we are looking at are the following:

  • How to handle sessions in WCF interfaces, services and clients
  • If you are using an object hierarchy for your data objects, and you are only using the base types in your operation contract specification of your interfaces, how do you ensure that your WSDL generated on the client side is aware of these derived types.
  • If you have an interface that returns a generic type (e.g. List<X>), how do you avoid that the generated proxy translates this into an array instead of a list?
  • How do I use svcUtil.exe to customize the generation of my proxy code?

The code for this article can be downloaded from the downloads section of this site.

Application Description

Requirements

In this case, we want to create an WCF service which maintains the state of a pet kennel (or coral). In this scenario the coral can only contain our cats or dogs. The service should provide the following functionality:

  1. Check in a pet (be it a cat or a dog) in the coral.
  2. Check out (remove) a pet from from the coral.
  3. Get a complete list of all of my pets that are currently in the coral.

WCF Server Implementation

The Data Contracts

A data contract describes how a CLR type will map to an XSD schema definition, which will ultimately become part of  the WSDL of our service. Data Contracts are the preferred way to enable serialization of complex types included in the the operation signatures of a service, be it as a parameter or as a return value.

All of our Data Contracts are defined in the PetTypes.dll assembly.

In this case, we have to entities to deal with:

  • Cat
  • Dog

Since both of these entities have a number of attributes in common, it makes sense to create a base class called Pet, which defines these common attributes. The class hierarchy used is shown below:

Overview1

So, some of the base attributes, such as FurColor, Name and BornAt at located at the base class, while the dog and cat-specific attributes are moved down to their specific classes.

To mark a class as a Data Contract, you use the DataContractAttribute attribute. It is always  a good idea to specify a namespace for the data contract, which reflects the current version of your code, as is shown below:

// This is our Pet base class [DataContract(Namespace = "http://footheory.com/KnownTypes/2007/07")] public class Pet {

In this case, we used the "2007/07" postfix, which will help us identify the version of the data contract later.

To include members in serialization you mark them with the DataMemberAttribute. Unlike with XML Serialization, the Default WCF DataContractSerializer uses an opt-in model, so your client will only see members marked with the DataMemberAttribute. Also, this process has nothing to do with the visibility of the member (public, protected or private).

Below is the complete code for the Pet base class:

1 using System; 2 using System.ServiceModel; 3 using System.Runtime.Serialization; 4 5 namespace PetTypes 6 { 7 // This is our Pet base class 8 [DataContract(Namespace = "http://footheory.com/KnownTypes/2007/07")] 9 public class Pet 10 { 11 #region private fields 12 13 private string m_name; 14 15 /// <summary> 16 /// Note that we might not exactly know when our pet was born 17 /// (for example, when it was adopted), hence we use a nullable 18 /// type here 19 /// </summary> 20 private DateTime? m_bornAt; 21 22 private string m_furColor; 23 24 #endregion private fields 25 26 #region public properties 27 28 [DataMember] 29 public string Name 30 { 31 get { return m_name; } 32 set { m_name = value; } 33 } 34 35 [DataMember] 36 public Nullable<DateTime> BornAt 37 { 38 get { return m_bornAt; } 39 set { m_bornAt = value; } 40 } 41 42 [DataMember] 43 public string FurColor 44 { 45 get { return m_furColor; } 46 set { m_furColor = value; } 47 } 48 49 #endregion public properties 50 51 } // class Pet 52 } 53

When you use the [DataMember] attribute, one of the first decisions that you have to make is wether you are going to use it only a data field, or on the property. While both approaches will work, I recommend applying them to the properties, because of the following reasons:

  • The client code will be easier to read and maintain. For example, you will be able to use ".Name", instead of ".m_name".
  • You get the benefit of extra validation (if implemented in the setters of the attributes).

Notice also that we have decided to make the m_bornAt field nullable, so no data value has to be provided by the client. Note that we use the C# shorthand

1 /// <summary> 2 /// Note that we might not exactly know when our pet was born 3 /// (for example, when it was adopted), hence we use a nullable 4 /// type here 5 /// </summary> 6 private DateTime? m_bornAt; 7

in the field definition, and the more formal:

1 [DataMember] 2 public Nullable<DateTime> BornAt 3 { 4 get { return m_bornAt; } 5 set { m_bornAt = value; } 6 } 7

notation at the property level. Note that there is no extra information that we need to provide in the [DataMember] attribute. In he resulting xsd schema generated for our service, BornAt will now be marked as nillable, as is shown below:

<xs:element minOccurs="0" name="BornAt" nillable="true" type="xs:dateTime" />

The Dog and Cat classes derive from the Pet classes, and simply add one attribute as is shown below:

1 // This class represents a Dog. Since a Dog "is a" Pet, we inherit 2 // directly from the Pet class 3 [DataContract(Namespace = "http://footheory.com/KnownTypes/2007/07")] 4 public class Dog : Pet 5 { 6 #region private fields 7 8 private bool m_hasPedigree; 9 10 #endregion private fields 11 12 #region public properties 13 14 // Of course our dog will have a pedigree! 15 // We don't deal with "mutts"! 16 [DataMember] 17 public bool HasPedigree 18 { 19 get { return m_hasPedigree = true; } 20 set { m_hasPedigree = value; } 21 } 22 23 #endregion public properties 24 25 } // class Dog

1 // Since some people say that a Cat "is a" pet (not sure I agree), we 2 // inherit this class directly from Pet 3 [DataContract(Namespace = "http://footheory.com/KnownTypes/2007/07")] 4 public class Cat : Pet 5 { 6 #region private fields 7 8 private bool m_listensToOwner = false; 9 10 #endregion private fields 11 12 #region public properties 13 14 // Cats never listen to their owner! 15 [DataMember] 16 public bool ListensToOwner 17 { 18 get { return m_listensToOwner; } 19 set { m_listensToOwner = false; } 20 } 21 22 #endregion public properties 23 } // class Cat 24

Note: There is absolutely no preference of the author towards the canine population, any appearance of this fact is purely coincidental ;-)

The Service Contract

We use the [ServiceContract] attribute to mark our interface as a service contract. Each exposed method in our service contract needs to be decorated with [OperationContract] attribute. For our Service Contract, we need to keep a couple of things in mind:

  1. We might want to use the same Namespace attribute value as we used for our data contract, to simplify versioning later.
  2. Since we want to maintain state in between calls, we need to have session support. For this purpose, the ServiceContractAttribute  offers the SessionMode enumeration, which can be set to the following values:
    • SessionMode.Allowed: This specifies that the contract supports session if the incoming binding supports them.
    • SessionMode.NotAllowed: This specifies that the contract never supports bindings that initiate sessions
    • SessionMode.Required: This specifies that the contract requires a sessionful binding. In this case an exception will be thrown in the binding is not configured to support sessions.

In our case, we want a session, so we can maintain the animals we have in our coral, so we use SessionMode = SessionMode.Required.

1 /// <summary> 2 /// This is our PetCoral Service. Note that this interface expects to be 3 /// implemented by a service that supports sessions, since we want to 4 /// suppport state 5 /// </summary> 6 [ServiceContract(Namespace = "http://footheory.com/KnownTypes/2007/07", 7 SessionMode = SessionMode.Required)] 8 public interface IPetCoral 9 { 10 /// <summary> 11 /// This method allows the owner to bring in a pet. 12 /// Because this method supports both cats and dogs, 13 /// we just use a "Pet" parameter 14 /// </summary> 15 /// <param name="pet"></param> 16 [OperationContract] 17 void BringPet(Pet pet); 18 19 /// <summary> 20 /// This operation allows us to take a pet home. 21 /// Again, this method does not care if you are 22 /// taking home a Dog or a cat 23 /// </summary> 24 /// <param name="pet"></param> 25 [OperationContract] 26 void TakePetHome(Pet pet); 27 28 /// <summary> 29 /// This method allows me to look at all of my 30 /// pets in the coral 31 /// </summary> 32 /// <returns></returns> 33 [OperationContract] 34 List<Pet> GetAllPets(); 35 36 } // interface IPetCoral

Note that none of these method needs to use the Dog or a Cat Types, but instead takes the more generic Pet type. This also makes our service interface more extensible, for example in the future we might want to add support for the PetPig class ;-).  Note that the GetAllPets() method returns the List<Pet> type, we will look at this again when we create the client proxy for our service.

The Service Implementation

The implementation of our IPetCoral interface is also located in the PetCoral.dll assembly.

When we create our service implementation class (PetCoralService.cs), we need to make sure that we use the correct instancing mode. Instancing modes in WCF control the way that service objects are allocated to a request. In our case, we need one single service object for each client, or in other words, we want an InstanceContextMode that is set to InstanceContextMode.PerSession. Other possible values of the InstanceContextMode enumeration are:

  • PerCall: In this case, a new service object is created for each call to the service. This is the model that you will very likely use in an enterprise environment, where you need a highly scalable, stateless solution.  In our case, we purposely avoided this mode to illustrate some session specifics, but I recommend that you use this instancing mode unless you have a very good reason not to do so.
  • Single: A single service object is created and used for all calls from all clients. This type of instancing model should only be used in very specific situations, since you are effectively creating a singleton, with all of it's associated concurrency issues. In a commercial environment, you are not likely to have a need for a WCF service with an instancing model set to InstanceContextMode.Single.

The InstanceContextMode is a parameter of the ServiceBehaviorAttribute, as is shown below:

1 /// <summary> 2 /// This is our service which implements our IPetCoral service. Since this 3 /// service maintains state for the caller, we use an instance mode of 4 /// "PerSession", so one instance of this object will be created per 5 /// caller 6 /// </summary> 7 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] 8 public class PetCoralService : IPetCoral 9 {

Otherwise, the implementation of the PetCoralService is very simple. All added pets are maintained in a generic List<Pet> instance  (which is why we need the "Per Session" behavior), as is is shown below:

1 #region private fields 2 3 /// <summary> 4 /// This list contains all of the Pets that are currently 5 /// in our Coral 6 /// </summary> 7 private List<Pet> m_petsInCoral = new List<Pet>(); 8 9 #endregion

The implementation of the BringPet() method simply adds the pet to our List<Pet>:

1 /// <summary> 2 /// This method allows the caller to bring in a Pet 3 /// </summary> 4 /// <param name="pet"></param> 5 public void BringPet(Pet pet) 6 { 7 m_petsInCoral.Add(pet); 8 9 } // method BringPet 10

The implementation of our TakePetHome() method will throw a FaultException if our List if empty (i.e. all Pets have been taken home). Otherwise, it uses Array.Find with an anonymous delegate to locate the pet, using name, the born date and the fur color of the Pet (we make the simple assumption that these attributes make a Pet unique). If the Pet is found, it is removed from the collection. If the Pet is not found, an FaultException<ArgumentOutOfRangeException> is thrown to indicate that the Pet could not be found.

Note that this post does not focus on exception handling in WCF, so we are using generic FaultExceptions here, without making them part of the service contract. Look for a future post with a lot more detail on this topic.

1 /// <summary> 2 /// This method allows the caller to take a pet home 3 /// </summary> 4 /// <param name="pet"></param> 5 public void TakePetHome(Pet petToTakeHome) 6 { 7 // First, we check if we have any pets left. If not, we throw 8 // a Fault Exception 9 if (m_petsInCoral.Count == 0) 10 { 11 throw new FaultException("All Pets have been taken home already"); 12 } 13 14 // Take the Pet Home 15 if (petToTakeHome != null) 16 { 17 Pet petFound = Array.Find<Pet>(m_petsInCoral.ToArray(), delegate(Pet pet) 18 { 19 if (pet.Name == petToTakeHome.Name && 20 pet.BornAt == petToTakeHome.BornAt && 21 pet.FurColor == petToTakeHome.FurColor) 22 { 23 return true; 24 } 25 else 26 { 27 return false; 28 } 29 }); 30 31 if (petFound != null) 32 { 33 m_petsInCoral.Remove(petFound); 34 } 35 else 36 { 37 throw new FaultException<ArgumentOutOfRangeException>( 38 new ArgumentOutOfRangeException("Pet Not Found")); 39 } 40 } 41 42 } // method TakePetHome 43

The implementation of the GetAllPets() method simply returns the generic List of Pets:

1 /// <summary> 2 /// This method returns all of my Pets in the Coral 3 /// </summary> 4 /// <returns></returns> 5 public List<Pet> GetAllPets() 6 { 7 return m_petsInCoral; 8 9 } // method GetAllPets 10

Hosting the Service

Now that we have a service implementation, we need a host. In this case, we have opted for Self-Hosting in a simple Console application, since it is very easy to debug.  Our Service will expose two endpoints:

  1. The first endpoint uses the netTcpBinding to expose the PetCoral.IPetCoral interface.
  2. The second endpoint uses a mexHttpBinding to expose the service metadata through the IMetadataExchange Interface.

The full app.config file for the service host is shown below:

<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.serviceModel> <services> <service name="PetCoral.PetCoralService" behaviorConfiguration="myBehavior"> <host> <baseAddresses> <add baseAddress="http://localhost:7000"/> <add baseAddress="net.tcp://localhost:7001"/> </baseAddresses> </host> <endpoint address="PetCoral" contract="PetCoral.IPetCoral" binding="netTcpBinding" /> <endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex" /> </service> </services> <behaviors> <serviceBehaviors> <behavior name="myBehavior"> <serviceMetadata httpGetEnabled="true"/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel> </configuration>

Building the Client

In this case, we will build a small WinForms client (Yes, I know I'm a big wimp, I am still getting up to speed on WPF, but I promise that the next client will be implemented in WPF).

First of all, we need to generate our proxy. Basically, you have two ways to generate a proxy for a service:

  1. You can right-click your client project and select "Add Service Reference" (the "girly-man" way).
  2. You can use svcutil.exe to manually generate the proxy (the "real way").

All kidding aside, I really think that as a WCF developer you should become intimately familiar with svcutil.exe, because of it's inherent power and flexibility.

 In it's simples form, we simply have to specify where the metadata of our service lives, and what the name is of the proxy and client configuration file we want to generate, as shown below:

svcutil /config:app.config /out:PetCoralServiceClient.cs http://localhost:7000/mex

You can run this command from any Visual Studio command prompt. Make sure that you run the host first, otherwise svcutil.exe will never be able to locate the metadata.

After this command completes, it will have create two new files:

  1. An app.config file with all of the client settings. You should be able to add that file to your project without any changes
  2. A called PetCoralServiceClient.cs, which contains our proxy code. 

However, when we open this file and take a look at it we notice that the code does contain a definition for the Pet data contract, as shown below:

1 public partial class Pet : object, System.Runtime.Serialization.IExtensibleDataObject 2 { 3 4 private System.Runtime.Serialization.ExtensionDataObject extensionDataField; 5 6 private System.Nullable<System.DateTime> BornAtField; 7 8 private string FurColorField; 9 10 private string NameField; 11 12 public System.Runtime.Serialization.ExtensionDataObject ExtensionData 13 { 14 get 15 { 16 return this.extensionDataField; 17 } 18 set 19 { 20 this.extensionDataField = value; 21 } 22 } 23 24 [System.Runtime.Serialization.DataMemberAttribute()] 25 public System.Nullable<System.DateTime> BornAt 26 { 27 get 28 { 29 return this.BornAtField; 30 } 31 set 32 { 33 this.BornAtField = value; 34 } 35 } 36 37 [System.Runtime.Serialization.DataMemberAttribute()] 38 public string FurColor 39 { 40 get 41 { 42 return this.FurColorField; 43 } 44 set 45 { 46 this.FurColorField = value; 47 } 48 } 49 50 [System.Runtime.Serialization.DataMemberAttribute()] 51 public string Name 52 { 53 get 54 { 55 return this.NameField; 56 } 57 set 58 { 59 this.NameField = value; 60 } 61 } 62 } 63

but nowhere are there any Cats or Dogs to be found! This is not good, because our client would like to work with both Cats and Dogs. This problem originates from the follow fact: The service description will only include data contracts that are known to the DataContractSerializer. By default, this means only data contracts explicitly included in at least on operation signature marked with the [OperationContract] attribute. When operations use a base type (like we did), other types that inherit those base types and interface are not known during serialization, and therefore, are not generated in the proxy.

To solve this problem, you need a way to tell the DataContractSerializer  about other polymorphic types that may be included in calls to such operations. This is achieved with known types.

There are basically three different ways to add these known types to the service contract:

  1. Apply one or more KnowTypeAttribute to a type indicating the polymorphic type with which it is compatible.
  2. Apply the ServiceKnownType attribute to the service contract or to a particular operation indicating any polymorphic types that should be supported.
  3. Configure known types globally for the DataContractSerializer.

Regardless of how you configure known types, the result is that the service description (and therefore your generated proxy) will included these types. Let's take a quick look at each of these approaches:

KnownTypeAttribute

Base types can supply a list of known types by applying one or more KnownTypeAttribute indicating related polymorphic types. In our case, this would mean that the add the following lines of code to the Pet class:

1 // This is our Pet base class 2 [DataContract(Namespace = "http://footheory.com/KnownTypes/2007/07")] 3 [KnownType(typeof(Dog))] 4 [KnownType(typeof(Cat))] 5 public class Pet 6

Now, if you rebuild, and re-run the host, and the svcutil.exe command, you will see both the Dog and Cat classes in the proxy:

1 public partial class Pet : object, System.Runtime.Serialization.IExtensibleDataObject 2 { 3 4 ... 5 } 6 7 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")] 8 [System.Runtime.Serialization.DataContractAttribute()] 9 public partial class Dog : footheory.com.KnownTypes._2007._07.Pet 10 { 11 12 .... 13 } 14 15 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")] 16 [System.Runtime.Serialization.DataContractAttribute()] 17 public partial class Cat : footheory.com.KnownTypes._2007._07.Pet 18 { 19 20 ... 21 } 22
ServiceKnownTypeAttribute

Another way to support known types is to apply the ServiceKnownTypeAttribute. This attribute can be applied to the entire service contract to associate known types with all operations, or to individual operation in order to control which types are associated with each. Below is an example where I applied the ServiceKnownAttribute to the contract itself:

1 [ServiceContract(Namespace = "http://footheory.com/KnownTypes/2007/07", 2 SessionMode = SessionMode.Required)] 3 [ServiceKnownType(typeof(Cat))] 4 [ServiceKnownType(typeof(Dog))] 5 public interface IPetCoral 6 {

Note that in this case you no longer need the KnownType attributes on the base Pet type. Now, if you run the host and svcutil, you will also get the Cat and Dog types in the generated proxy.

Declarative known types

In some cases you may not know all of those known types when you build he service and related data contracts. It is also possible that you add new polymorphic types after you publish the service. To add known type support with recompiling assemblies that contain service contracts or data contracts, you can reconfigure then declaratively in the <system.runtime.serialization> section of your app.config file. Please refer to the the WCF documentation for this more seldom used practice.

further tweaking svcutil.exe

When you look at the output of svcutil.exe, you will notice that all of the classes are  placed in a namespace that corresponds to the namespace specified in your data- and service contracts, for example:

1 namespace footheory.com.KnownTypes._2007._07 2 { 3 using System.Runtime.Serialization; 4 5 6 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")] 7 [System.Runtime.Serialization.DataContractAttribute()] 8 [System.Runtime.Serialization.KnownTypeAttribute(typeof(footheory.com.KnownTypes._2007._07.Cat))] 9 [System.Runtime.Serialization.KnownTypeAttribute(typeof(footheory.com.KnownTypes._2007._07.Dog))] 10 public partial class Pet : object, System.Runtime.Serialization.IExtensibleDataObject 11 { 12

Sometimes, you just want to use a simpler namespace. You can use the "/n" switch (or the longer "/namespace" version), for example, to ensure that all of our types are in the PetCoral namespace, we use the following command:

svcutil /config:app.config /out:PetCoralServiceClient.cs /n:*,PetCoral http://localhost:7000/mex

Now, when you look at the proxy, you see the more friendly "PetCoral" namespace, as shown below:

1 namespace PetCoral 2 { 3 using System.Runtime.Serialization; 4 5 6 [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization", "3.0.0.0")] 7 [System.Runtime.Serialization.DataContractAttribute(Namespace="http://footheory.com/KnownTypes/2007/07")] 8 [System.Runtime.Serialization.KnownTypeAttribute(typeof(PetCoral.Cat))] 9 [System.Runtime.Serialization.KnownTypeAttribute(typeof(PetCoral.Dog))] 10 public partial class Pet : object, System.Runtime.Serialization.IExtensibleDataObject 11 {

Finally, in our service contract we specified that the GetAllPets() method returns a List<Pets> type as shown below:

/// <summary> /// This method allows me to look at all of my /// pets in the coral /// </summary> /// <returns></returns> [OperationContract] List<Pet> GetAllPets();

but, we when look in the generated proxy, we see that the generic collection has been translated into an array:

1 [System.ServiceModel.OperationContractAttribute(Action="http://footheory.com/KnownTypes/2007/07/IPetCoral/GetAllPets", 2 ReplyAction="http://footheory.com/KnownTypes/2007/07/IPetCoral/GetAllPetsResponse")] 3 PetCoral.Pet[] GetAllPets();

You can use the "/ct" or the longer "/collectiontype" switch to specify the type that should be used. If you do so, make sure that you reference the type in which this type is located, as is shown below:

1 svcutil /r:C:\Windows\Microsoft.NET\Framework\v2.0.50727\System.dll 2 /ct:System.Collections.Generic.List`1 3 /config:app.config 4 /out:PetCoralServiceClient.cs 5 /n:*,PetCoral 6 http://localhost:7000/mex

note the slightly strange '1 notation when specifying the collection type.

Now, after running svcutil, you should have the correct return type:

1 [System.ServiceModel.OperationContractAttribute'( 2 Action="http://footheory.com/KnownTypes/2007/07/IPetCoral/GetAllPets", 3 ReplyAction="http://footheory.com/KnownTypes/2007/07/IPetCoral/GetAllPetsResponse")] 4 System.Collections.Generic.List<PetCoral.Pet> GetAllPets(); 5

Summary

This post addressed a number of issues, all of which are very easy to solve, but might cause you some headaches in the process. If you have any additional tips or corrections, please do not hesitate to contact me.

Published