|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
IntroductionIn 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. BackgroundMaking the decision to share types between 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 one application I am working on, I am controlling both the client and 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 Services 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 Code Project articles regarding WCF, check out this link. Source Code OverviewThe 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 client. Both the service and client projects are console applications. The configuration for both the client and service are hard coded for this example.
Shared TypesThere are two assemblies containing types to be shared between the service and client. TheShareTypes.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 Note that the collections are marked with the The 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 );
}
}
}
}
ServiceThe 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
The service project also defines some types that are not shared between the client and service: [DataContract]
public class NonSharedType {
public NonSharedType() {
Item = new NonSharedCollection();
}
[DataMember]
public NonSharedCollection Item;
}
public class NonSharedCollection: Collection< int > {}
Hosting the serviceThe following code hosts the service. To enable the 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 the 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();
}
}
ClientSvcUtil.exe command lineBefore 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 client and 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 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 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 If a collection is marked with the 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
For the two methods using shared types, their return values are from the 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 CodeThe client code to call the service follows (with interspersed comments). All types being shared from the common assemblies are prefixed with 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
The third method call returns an instance of 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 client. This method returns an array of integers. And since the 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 screen shot 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).
A Design NoteAs 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:
So if you want to share types between the service and 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 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
References
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||