Click here to Skip to main content
Click here to Skip to main content
Go to top

Sharing WCF Collection Types Between Services and Clients

, 4 Sep 2007
Rate this:
Please Sign up or sign in to vote.
Sharing types between WCF services and clients, including multiple assemblies and collection types.

Introduction

In this article, I will illustrate an example of sharing types between services and clients in Windows Communication Foundation (WCF), including types in multiple assemblies and custom collection types.

Background

Making the decision to share types between a WCF service and clients can be controversial. Service Oriented Architecture (SOA) purists will tell you that you should never share types between services and clients; you should only share contracts. So, you should carefully consider the decision to share types between clients and services. In an application I am working on, I am controlling both the client and the service, and will for the foreseeable future. As I started implementation, it quickly became obvious that I would have to copy (or factor out in some way) a fair amount of code to support sharing only contracts, so I decided to share the types themselves on this project.

After I made the decision to share types, one of the resources that pointed me in the right direction was an article on The Code Project by Mike Robsiki titled Sharing Types Between WCF Service and Client. It shows the basic syntax for using SvcUtil.exe to generate a proxy using shared types, but it does not cover topics such as using types from multiple assemblies, or collection types. This article addresses these additional topics.

This article assumes you have a basic understanding of WCF. For other CodeProject articles regarding WCF, check out this link.

Source Code Overview

The source code for this article contains a Visual Studio 2005 solution with four projects: a service (ShareTypes.Service), client (ShareTypes.Client), and two common class libraries (ShareTypes.Common1, ShareTypes.Common2). The common libraries contain the classes that are shared between the service and the client. Both the service and the client projects are console applications. The configuration for both the client and service are hard-coded for this example.

Shared Types

There are two assemblies containing types to be shared between the service and the client. The ShareTypes.Common1 assembly defines three classes:

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

namespace ShareTypes.Common {
   [DataContract]
   public class SharedType1 {
      public SharedType1() {
         Item = new SharedCollection1();
         ItemWithContractNotShared = new CollectionWithContractNotShared();
      }

      [DataMember]
      public SharedCollection1 Item;

      [DataMember]
      public CollectionWithContractNotShared ItemWithContractNotShared;
   }

   [CollectionDataContract]
   public class SharedCollection1:Collection<string /> {
      public void AddArray( string[] toAdd ) {
         foreach ( string s in toAdd ) {
            Add( s );
         }
      }
   }

   [CollectionDataContract]
   public class CollectionWithContractNotShared: Collection<string /> {
      public void AddArray( string[] toAdd ) {
         foreach ( string s in toAdd ) {
            Add( s );
         }
      }
   }
}

The SharedType1 class is decorated with the DataContract attribute, and has fields marked with the DataMember attribute. Each field is a collection. SharedCollection1 is intended to be shared between the client and server, while CollectionWithContractNotShared will not be shared between the client and the service. For convenience, in this example, I have used public fields rather than private fields with corresponding public properties. This is not recommended for production applications.

Note that the collections are marked with the CollectionDataContract attribute rather than the DataContract attribute. This is necessary (but not sufficient) to share collection types between services and clients, and affects the generated proxy, as will be seen later.

The ShareTypes.Common2 assembly defines two classes, both of which are to be shared between the client and the service:

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

namespace ShareTypes.Common {
   [DataContract]
   public class SharedType2 {
      public SharedType2() {
         Item = new SharedCollection2< int >();
      }

      [DataMember]
      public SharedCollection2< int > Item;
   }

   [CollectionDataContract]
   public class SharedCollection2< T >: Collection< T > {
      public void AddArray( T[] toAdd ) {
         foreach ( T o in toAdd ) {
            Add( o );
         }
      }
   }
}

Service

The service itself is very simple. Each method accepts one of the types with a collection as a public field, and returns a collection from the instance passed as a parameter. Each method echoes the collection values passed as a parameter. Here is the service interface and implementation:

[ServiceContract]
public interface IService {
   [OperationContract]
   SharedCollection1 UseSharedTypes1( SharedType1 sharedType );

   [OperationContract]
   SharedCollection2< int > UseSharedTypes2( SharedType2 sharedType );

   [OperationContract]
   CollectionWithContractNotShared 
     UseCollectionWithContractNotShared( SharedType1 sharedType );

   [OperationContract]
   NonSharedCollection UseNonSharedTypes( NonSharedType nonSharedType );
}

public class Service: IService {
   public SharedCollection1 UseSharedTypes1( SharedType1 sharedType ) {
      return sharedType.Item;
   }

   public SharedCollection2<int> UseSharedTypes2( SharedType2 sharedType ) {
      return sharedType.Item;
   }

   public CollectionWithContractNotShared 
          UseCollectionWithContractNotShared( SharedType1 sharedType ) {
      return sharedType.ItemWithContractNotShared;
   }

   public NonSharedCollection UseNonSharedTypes( NonSharedType nonSharedType ) {
      return nonSharedType.Item;
   }
}

The service project also defines some types that are not shared between the client and the service:

[DataContract]
public class NonSharedType {
   public NonSharedType() {
      Item = new NonSharedCollection();
   }

   [DataMember]
   public NonSharedCollection Item;
}

public class NonSharedCollection: Collection<int> {}

Hosting the Service

The following code hosts the service. To enable SvcUtil.exe to be able to create a proxy for our service, we need to enable metadata exchange. To enable metadata exchanges, we need to add ServiceMetadataBehavior to the service and add a service endpoint. (Note the use of the static CreateMexTcpBinding method on the MetadataExchangeBindings class to create a binding for metadata exchange. There are methods to create bindings for various protocols.)

public static void Main() {
   Uri baseAddress = new Uri( "net.tcp://localhost:9042/Service" );
   Uri mexAddress = new Uri( "mex", UriKind.Relative );
   using ( ServiceHost serviceHost = 
           new ServiceHost( typeof( Service ), baseAddress ) ) {
      NetTcpBinding binding = new NetTcpBinding();

      serviceHost.AddServiceEndpoint( typeof ( IService ), binding, baseAddress );

      // Add metadata exchange behavior to the service
      serviceHost.Description.Behaviors.Add( new ServiceMetadataBehavior() );

      // Add a service Endpoint for the metadata exchange
      serviceHost.AddServiceEndpoint( typeof ( IMetadataExchange ), 
          MetadataExchangeBindings.CreateMexTcpBinding(), mexAddress );

      // Run the service
      serviceHost.Open();
      Console.WriteLine( "Service started. Press enter to terminate service." );
      Console.ReadLine();
      serviceHost.Close();
   }
}

Client

SvcUtil.exe Command Line

Before writing client code, we must generate a proxy class for the service. To share types between a service and client, you must generate the proxy using the SvcUtil.exe command line tool, rather than adding a service reference in Visual Studio. (In the Orcas version of Visual Studio, more SvcUtil.exe options will be available within the IDE and this may no longer be necessary). The following shows the command line required to generate the client proxy. It is included in the client project source in UpdateServiceReference.bat (in the batch file, it is all on one line, but is wrapped here for readability):

"c:\Program Files\Microsoft SDKs\Windows\v6.0\Bin\SvcUtil" 
      /language:cs 
      /out:ServiceClient.cs  
      /namespace:*,ShareTypes.Client 
      /noconfig
      /reference:..\ShareTypes.Common1\bin\Debug\ShareTypes.Common1.dll
      /reference:..\ShareTypes.Common2\bin\Debug\ShareTypes.Common2.dll
      /collectionType:ShareTypes.Common.SharedCollection1 
      /collectionType:ShareTypes.Common.SharedCollection2`1 
      net.tcp://localhost:9042/Service/mex

The options relevant to sharing types between the client and the service are the reference and collectionType switches. The reference switch (short form: /r) instructs SvcUtil.exe to use types in the referenced assembly directly, rather than generating client side versions of them; basically, this prevents SvcUtil.exe from generating code for the types in these assemblies. You can specify the reference switch multiple times. For types that are not collections, this is all you need to do to share types between clients and services.

For collection types, there is an additional step necessary to share types between the client and the service, which is adding the collectionType switch (short form: /ct). Each occurrence of the collectionType switch identifies a collection type that will be shared between the service and the client. The collectionType switch must identify a type that is defined within an assembly specified in a reference switch.

The expression in the collectionType switch for SharedCollection2 is followed by a backquote (`) and a 1. For generic types, you must specify the number of generic parameters with a backquote followed by the number of generic parameters (one for SharedCollection2). Do not confuse the backquote (`) with the single quote ('). (On my keyboard, the backquote is under the tilde (~)).

If a collection is marked with the CollectionDataContract attribute and is in an assembly identified in a reference switch, but is not identified in a collectionType switch, it will not be shared between the client and the service. The CollectionWithContractNotShared class in the ShareTypes.Common1 assembly demonstrates this. It is marked with the CollectionDataContract attribute, but is not mentioned explicitly in a collectionType switch. Because it is not in a collectionType switch, there is a client side type generated for it.

The following code excerpt shows a portion of the generated client proxy, with the attributes omitted for clarity:

public interface IService
{
    ShareTypes.Common.SharedCollection1 UseSharedTypes1(
                      ShareTypes.Common.SharedType1 sharedType);
    ShareTypes.Common.SharedCollection2<int> UseSharedTypes2(
               ShareTypes.Common.SharedType2 sharedType);
    ShareTypes.Client.CollectionWithContractNotShared 
      UseCollectionWithContractNotShared(ShareTypes.Common.SharedType1 sharedType);
    int[] UseNonSharedTypes(ShareTypes.Client.NonSharedType nonSharedType);
}

For the two methods using shared types, their return values are from the ShareTypes.Common namespace. For the method returning a CollectionWithContractNotShared, it is referencing the class generated on the client side as described above. The final method, UseNonSharedTypes, illustrates the default behavior when a collection is returned from a service, which is to output the results as an array. So using the CollectionDataContract attribute on the CollectionWithContractNotShared forces a client side class to be generated deriving from List<T>:

public class CollectionWithContractNotShared : System.Collections.Generic.List<string />
{
}

For an in-depth discussion of collection types in data contracts, refer to the MSDN article: Collection Types in Data Contracts.

Client Code

The client code to call the service follows (with interspersed comments). All types being shared from the common assemblies are prefixed with Common to identify the common namespace. The first two method calls demonstrate the usage of shared types, with parameter and return value types prefixed by Common.

private static void Main() {
   EndpointAddress endpointAddress = 
            new EndpointAddress( "net.tcp://localhost:9042/Service" );
   NetTcpBinding binding = new NetTcpBinding();

   using ( ServiceClient proxy = new ServiceClient( binding, endpointAddress ) ) {
      Common.SharedType1 sharedType1 = new Common.SharedType1();
      sharedType1.Item.AddArray(  new string[] { "1", "2", "3", "4", "5" } );
      Console.WriteLine( 
              string.Format( "Calling UseSharedTypes1 with collection values {0}", 
              GetValues( sharedType1.Item ) ) );
      Common.SharedCollection1 sharedCollection1 = proxy.UseSharedTypes1( sharedType1 );
      Console.WriteLine( string.Format( "Returned UseSharedTypes1 values {0}", 
              GetValues( sharedCollection1 ) ) );
      Console.WriteLine( "" );

      Common.SharedType2 sharedType2 = new Common.SharedType2();
      sharedType2.Item.AddArray( new int[] { 3, 2, 1 } ) ;
      Console.WriteLine( 
              string.Format( "Calling UseSharedTypes2 with collection values {0}", 
              GetValues( sharedType2.Item ) ) );
      Common.SharedCollection2<int> sharedCollection2 = proxy.UseSharedTypes2( sharedType2 );
      Console.WriteLine( string.Format( "Returned UseSharedTypes2 values {0}", 
              GetValues( sharedCollection2 ) ) );
      Console.WriteLine( "" );

The third method call returns an instance of CollectionWithContractNotShared. It does not use a common type as a return value; it uses one generated on the client side. In this case, we can still use the AddArray method on the SharedType1 instance to add values going into the method, but the AddArray method is not available on the returned collection because it is referencing the proxy generated type (not prefaced by Common).

sharedType1 = new Common.SharedType1();
sharedType1.ItemWithContractNotShared.AddArray( new string[] { "1", "2" } );
Console.WriteLine( string.Format( 
      "Calling UseCollectionWithContractNotShared with collection values {0}", 
      GetValues( sharedType1.ItemWithContractNotShared ) ) );
CollectionWithContractNotShared collectionWithContractNotShared = 
              proxy.UseCollectionWithContractNotShared( sharedType1 );
Console.WriteLine( string.Format( 
      "Returned UseCollectionWithContractNotShared values {0}", 
      GetValues( collectionWithContractNotShared ) ) );
Console.WriteLine( "" );

The last method call shows the standard behavior with types that are not shared between the service and the client. This method returns an array of integers. And since the NonSharedType referenced was generated on the client side, the Item collection property is generated as an array of integers.

      NonSharedType nonSharedType = new NonSharedType();
      nonSharedType.Item = new int[] { 5, 4, 3, 2, 1 };
      Console.WriteLine( 
              string.Format( "Calling UseNonSharedTypes with collection values {0}", 
              GetValues( nonSharedType.Item ) ) );
      int[] collectionNonShared = proxy.UseNonSharedTypes( nonSharedType );
      Console.WriteLine( string.Format( "Returned UseNonSharedTypes values {0}", 
              GetValues( collectionNonShared ) ) );
      Console.WriteLine( "" );

      Console.WriteLine( "Press enter to continue." );
      Console.ReadLine();
   }
}

The following screenshot represents the results of running the client program. You must first start the service application before you can run the client (or the batch file to create the client proxy).

Result of running client program

A Design Note

As I mentioned at the beginning of the article, sharing types between a WCF service and client can be controversial, and many people frown upon ever doing it. However, as you can see from the code to share types on the client side, the decision to share types is completely controlled by the client. If you omit the reference and collectionType switches in the call to SvcUtil.exe, you are no longer sharing types with the service. There are two important points here:

  • There is nothing special in the WCF service interface or implementation to allow type sharing with the client.
  • Because you share types between a service and a client, it does not mean you must share types between that service and all clients. So you can have one .NET client that shares types, and other clients that do not share types.

So if you want to share types between the service and a client, go ahead, there's little risk in doing so, as long as you do nothing special in the service that assumes types will be shared with the client. You should also isolate the types to be shared between the service and the client into separate assemblies, and ensure these assemblies have as few dependencies as possible (for example, these assemblies should probably not contain data access). Otherwise, you may find that you need unwanted assemblies on the client side.

History

  • September 4, 2007 - Initial publication.

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

JoeWirtley
Web Developer
United States United States
Joe Wirtley is an independent consultant with twenty years software development experience. He currently works as a .NET architect and developer on both smart client (WPF, WCF) and web (ASP.NET) applications. Before .NET, Joe worked extensively in Delphi and Clipper. He is a member of several user groups in the Cincinnati-Dayton, OH area and has done presentations at user group meetings, Dayton-Cincinnati Code Camp, Day of .NET, and CodeMash.

You can read his blog at http://joewirtley.blogspot.com/ and see his personal site at http://www.wirtley.net.


Comments and Discussions

 
QuestionOne question about collections in WCF Pinmembereyanson4-Jan-11 14:20 
GeneralGreat article! Pinmemberadontz5-Feb-10 22:53 
GeneralThanks that is awesome article PinmemberVishal Bajoria13-Jan-10 7:42 
GeneralThank you, this did the trick for me PinmemberStefano Ricciardi12-May-09 1:06 
GeneralNo proxy.cs file getting generated when use /r PinmemberJigar B Patel23-Apr-08 23:17 
QuestionGenerating constants using svcutil Pinmembersyamala_198111-Dec-07 19:18 
GeneralWell written and insightful. PinmemberDaniel Vaughan6-Dec-07 18:34 
QuestionWhy not add a reference the types on the client? Pinmemberuwittig14-Sep-07 3:28 
AnswerRe: Why not add a reference the types on the client? PinmemberJoeWirtley14-Sep-07 3:56 
AnswerRe: Why not add a reference the types on the client? PinmemberPete O'Hanlon14-Sep-07 4:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140916.1 | Last Updated 4 Sep 2007
Article Copyright 2007 by JoeWirtley
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid