WCF and Client Proxies

Common Considerations for Proxy Generation

13 Min Read
ITPro Today logo

This column explores how to build Web services with WCF in.NET 3.0 and 3.5. In this installment, I ll talk about proxy generationtechniques. If you have questions about migrating your existing ASMX or WSE Webservices to WCF, or questions regarding WCF as it relates to Web services,please send them to [email protected]!

When developers think about WCF, they primarily concernthemselves with the server side of the equation: how to design, build, anddeploy reliable and scalable services for an enterprise system. Certainly thisis the starring role for WCF; but where there are services, there must also beclients consuming those services. There are also design considerations for theclient that impact your development process, available tools, and generalcoding practices as related to WCF proxy classes. In this article, I ll discussthe proxy generation results achieved with Visual Studio 2008 (Beta 2), discussthe pros and cons of using generated or hand-rolled proxies, and discussapproaches for sharing service metadata.

 

Proxy Generation with Visual Studio 2008

You can generate a proxy to consume a service using thecommand-line tool SvcUtil (svcutil.exe) or using Add Service Reference fromSolution Explorer in Visual Studio. SvcUtil is a fully featured proxy generatorthat supports the following key features:

  • Generating proxies from a WS-MetadataExchange endpoint, or from a static WSDL document.

  • Generating asynchronous proxies to improve perceived performance for client applications.

  • Generating configuration files that describe each service endpoint and the correct protocols for a proxy to reach each endpoint.

  • Generating proxies that reference types from pre-existing assemblies.

  • Controlling the collection type preferences proxies use for client coding against arrays and dictionaries.

In addition to these key features, you can generate aservice type with associated data contracts from an existing WSDL document,control the choice of serializer, merge configuration files, and a few othertasks.

Command-line generation of proxies is important inenvironments that rely on scripts to generate and update proxy code, and isnecessary when the desired results can t be accomplished using Visual Studio.The WCF Orcas extension for Visual Studio 2005 supports generating a basicproxy using Add Service Reference but it doesn t give you control overSvcUtil options during the process. Visual Studio 2008 includes the latest WCFtooling for proxy generation via Add Service Reference, providing a way toreach several key SvcUtil options (listed above) from a friendly dialog box.

Figure 1 illustrates the first dialog box you see when youinvoke Add Service Reference from Solution Explorer. As before, you specify themetadata exchange endpoint for the service (metadata exchange must be enabledfor this to work) and provide a name for the reference, which becomes part ofthe generated CLR namespace. Unlike before, you can now discover serviceswithin the current solution, and view information about the service endpointsbefore adding the reference.


Figure 1: The Add Service Referencedialog box in Visual Studio 2008 (Beta 2).

From the Advanced button in this dialog box you can accessadditional features, as shown in Figure 2. This includes control over thevisibility of classes generated, asynchronous proxy generation, array anddictionary types used by the proxy, leveraging existing assemblies for datacontract type definitions, and other features, such as creating messagecontracts and generating a traditional .NET 2.0 Web reference instead of aservice reference.


Figure 2: The Service ReferenceSettings dialog box in Visual Studio 2008 (Beta 2).

Several of these features make a big difference to clientdevelopers, removing the need to go to the command line and use SvcUtil. Likely,the most convenient of these new features is the ability to leveragepre-existing assemblies and reuse the same data contracts instead of generatingcopies at the client. I ll talk more about this scenario shortly. It is alsovery convenient to be able to control collection and dictionary types,particularly if you want to enable data binding with controls in the client UIwith BindingList. Another very common need is to create asynchronous operationsto simplify multithreading at the client to improve perceived performance.Having these features built-in to the Add Service Reference dialogs is a realtime saver, as it is always a pain to move out of the development environmentto generate proxies with the command line.

 

Proxy Generation vs. ChannelFactory

Whether you generate proxies from the command line, orthrough the Visual Studio IDE, you are using SvcUtil. By default, severalthings are created from proxy generation: a configuration file with settings toinvoke each service endpoint, a proxy wrapper class that encapsulates calls toeach service operation, a copy of the service contract, and a copy of all datacontracts, message contracts, and fault contracts necessary to support calls toand from service operations.

Consider the service contract and data contract in Figure3. The resulting code (not including endpoint configuration) from proxygeneration is shown in Listing One.

[ServiceContract(Namespace = "http://www.thatindigogirl.com/ samples/VS2008")]public interface IMessagingService{  [OperationContract] void SendMessage(MessageInfo message);}[DataContract(Namespace = "http://schemas.thatindigogirl.com/ samples/VS2008")]public class MessageInfo{  [DataMember(IsRequired=true, Order=0)] public Guid Id { get; set; }  [DataMember(IsRequired = true, Order = 1)] public string Subject { get; set; }  [DataMember(IsRequired = true, Order = 2)] public string Message { get; set; }}

Figure 3: Servicecontract and data contract for the MessagingService.

The benefits of generating proxies using SvcUtil include:

  • All the necessary metadata required to communicate with the service is generated for you.

  • Data contracts implement IExtensibleDataObject for version tolerance and INotifyPropertyChanged to support client-side data binding.

  • A class (proxy) that wraps the underlying client communication channel is created for you, thus you need only scope the proxy and begin calling service operations with it.

  • The proxy provides access to other underlying channel properties, such as SessionId and Open and Close methods.

  • This model decouples clients and services so clients and services can be versioned separately. Changes to the service contract or related data contracts will not impact client compilation, but may affect the client at run time if changes are not backward compatible.

  • Endpoint configuration necessary to initialize the proxy is automatically generated.

The drawbacks of generating proxies with SvcUtil include:

  • The code is generally more cluttered. Data contracts include property notification features you may not use. Service contracts expand several OperationContractAttribute values that could be inferred (Action, ReplyAction), and add ProtectionLevel settings even if the service doesn t require it. Both have superfluous debugging and code generation attributes.

  • Although generated data contracts will serialize properly to communicate with the service, the object model may not match that of the service type definition because it is derived from the WSDL schema. Programmers working on both sides must learn two object models as a result.

  • Generating proxies from WSDL decouples service and client development. If you own both sides, that could mean clients aren t notified at compile time of a change they should be updating their code to reflect in the service or data contracts. They ll discover the problem at run time only if backward compatibility is an issue. If you want to keep both sides in sync, this may not be ideal.

  • If you add code to the generated proxy to catch certain types of exceptions after each call, and recreate the inner channel wrapped by the proxy in case of a faulted channel (or, any other code that can t be provided in a partial class definition), regenerating the proxy will erase this custom code.

As an alternative to generating the proxy with SvcUtil,you can construct the channel directly using ChannelFactory(DuplexChannelFactory where callbacks are involved). Thus, the clientcoding experience would change from using the generated proxy like this:

ServiceReference.MessagingServiceClient proxy = new MessagingServiceClient("wsHttp");MessageInfo msg = new MessageInfo { Id = Guid.NewGuid(), Subject = string.Format("Message {0}", ++m_counter), Message = string.Format("This is message {0}", m_counter) };proxy.SendMessage(msg); to creating the channel by hand like this: ChannelFactory factory = new ChannelFactory("wsHttp");IMessagingService proxy = factory.CreateChannel();MessageInfo msg = new MessageInfo { Id = Guid.NewGuid(), Subject = string.Format("Message {0}", ++m_counter), Message = string.Format("This is message {0}", m_counter) };proxy.SendMessage(msg); 

But, this also implies that the metadata from Figure 3 is somehowavailable to the client application, and there was a way to generate the clientendpoint configuration necessary to initialize the channel. When you own bothsides, this may be done by sharing metadata assemblies between client andservice developers, and communicating endpoint requirements in some other wayrather than generating them with SvcUtil.

The benefits of this approach include:

  • Less overall clutter in the client code.

  • More control over proxy wrapper code needed for the client to handle faulted channels and other customizations.

  • Clients and services share assemblies, thus are both immediately updated when changes are made.

  • Developers work with the same object model at the client and service for metadata types.

The drawbacks are:

  • This raw approach to creating the channel factory and channel may not be perceived as friendly as the generated proxy.

  • You don t have access to channel properties such as SessionId (from IContextChannel) or Open and Close methods (from ICommunicationObject) without casting explicitly to those interfaces first. The generated proxy base class (ClientBase or DuplexClientBase) makes these properties directly available.

As a general rule, when you own both sides you ll likelyfind it more useful to share metadata assemblies between clients and services (asI ll discuss shortly). But, when you want versioning between clients andservices to be independent, or if you are consuming services you don t own,proxy generation makes it the best approach to creating the initialconfiguration, metadata, and proxy wrapper necessary to communicate with theservice.

 

Sharing Metadata

Clients require service metadata to build a channel tocommunicate with services. That metadata includes definitions for the servicecontract and any related data contracts, message contracts, fault contracts, orotherwise serializable types leveraged by service operations. You can generatethis metadata entirely with SvcUtil, generate parts of it while sharing somemetadata assemblies, or take the approach of sharing all metadata assembliesbetween clients and services. In this section, I ll discuss these approaches.

Figure 4 illustrates the result of proxy generation as I vediscussed thus far. In this case, the client relies solely on WSDL to generatecopies of the service contract, and any other contracts (including datacontracts). In addition, a proxy class is created to simplify client codingefforts.


Figure 4: Proxy generation usingSvcUtil by default doesn t share assemblies.

You can also use SvcUtil to generate the proxy, whilesharing metadata libraries for your data contracts with the service. Thishybrid result is illustrated in Figure 5, where the proxy and service contractare still generated, but data contract assemblies are shared. Prior to VisualStudio 2008 you could only achieve this using SvcUtil with the /r option:

svcutil /d:[output path] /o:proxy.cs /r:Messaging.Entities.dll http://localhost:8000

Now, Visual Studio 2008 will automatically reuse typesfrom referenced assemblies (as shown by the settings in Figure 2). That meansall you must do is reference the data contract assemblies you want to share(such as Messaging.Entities), prior to generating the service reference.

NOTE: Even if you reference assemblies that contain messagecontracts or service contracts you want to share, SvcUtil will only look fordata contracts and other serializable types that are used by service operationsor fault contracts.


Figure 5: Proxy generation usingSvcUtil sharing data contract assemblies.

If you want to achieve a result where client and servicedevelopers both work with the same object model for all metadata includingservice contracts, message contracts, data contracts, and other serializabletypes you won t use SvcUtil to generate your client proxy. Instead, you lluse ChannelFactory or DuplexChannelFactory as appropriate,and share all metadata assemblies. Figure 6 illustrates this result.


Figure 6: Sharing metadataassemblies between client and service.

NOTE: In general, I recommend you break up your projectsso you have separate assemblies for service contracts, message contracts, faultcontracts, and data contracts or other serializable entities. This makes iteasier to share assemblies down the road, without sharing service logic withclients.

 

Conclusion

Proxy generation techniques have definitely improved inVisual Studio 2008, making it easier to pass custom options to the SvcUtilproxy generation process using a simple dialog box. This definitely streamlinesproxy generation efforts for common scenarios such as customization ofcollection and dictionary types, creating asynchronous proxies, or sharing datacontract libraries. In the latter case, it is most likely that you own bothsides: the client and service. This often means you want to share more thanjust data contract assemblies, but also those that contain metadata for servicecontracts and message contracts (to keep developers working with a consistentobject model). In this case, you can skip proxy generation altogether andcreate the channel directly losing direct access to a few additionalproperties that the generated proxy provided.

Regardless of your approach, you ll most likely want tocreate your own client proxy wrappers that can encapsulate common exceptionhandling needs and recovery from faulted channels. I ll discuss this techniquein the next installment of this column. Until then, enjoy!

Code for thisarticle is available at http://www.dasblonde.net/downloads/aspprodec07.zip.The code is based on Visual Studio 2008 (Beta 2).

 

Michele LerouxBustamante is Chief Architect of IDesign Inc., Microsoft Regional Directorfor San Diego, Microsoft MVP for Connected Systems, and a BEA TechnicalDirector. At IDesign Michele provides training, mentoring, and high-endarchitecture consulting services focusing on Web services, scalable and securearchitecture design for .NET, federated security scenarios, Web services,interoperability, and globalization architecture. She is a member of theInternational .NET Speakers Association (INETA), a frequent conferencepresenter, conference chair for SD West, and is frequently published in severalmajor technology journals. Michele is also on the board of directors for IASA(International Association of Software Architects), and a Program Advisor toUCSD Extension. Her latest book is Learning WCF(O Reilly, 2007); see her book blog at http://www.thatindigogirl.com.Reach her at mailto:[email protected] orvisit http://www.idesign.net and her mainblog at http://www.dasblonde.net.

Begin Listing One Generated servicecontract, data contract, and proxy wrapper for MessagingService from Figure 3

namespace WinClient.ServiceReference { using System.Runtime.Serialization; using System;  [System.Diagnostics.DebuggerStepThroughAttribute()]  [System.CodeDom.Compiler.GeneratedCodeAttribute(   "System.Runtime.Serialization", "3.0.0.0")]  [System.Runtime.Serialization.DataContractAttribute(    Name="MessageInfo", Namespace=    "http://schemas.thatindigogirl.com/samples/VS2008")]  [System.SerializableAttribute()] public partial class MessageInfo : object,    System.Runtime.Serialization.IExtensibleDataObject,    System.ComponentModel.INotifyPropertyChanged {      [System.NonSerializedAttribute()]     private System.Runtime.Serialization.       ExtensionDataObject extensionDataField;     private System.Guid IdField;     private string SubjectField;     private string MessageField;      [global::System.ComponentModel.BrowsableAttribute(        false)]     public System.Runtime.Serialization.ExtensionDataObject        ExtensionData {         get {             return this.extensionDataField;         }         set {             this.extensionDataField = value;         }     }      [System.Runtime.Serialization.DataMemberAttribute(        IsRequired=true)]     public System.Guid Id {         get {             return this.IdField;         }         set {             if ((this.IdField.Equals(value) != true)) {                 this.IdField = value;                 this.RaisePropertyChanged("Id");             }         }     }      [System.Runtime.Serialization.DataMemberAttribute(        IsRequired=true)]     public string Subject {         get {             return this.SubjectField;         }         set {             if ((object.ReferenceEquals(this.SubjectField,                  value) != true)) {this.SubjectField = value;                   this.RaisePropertyChanged("Subject");             }         }     }      [System.Runtime.Serialization.DataMemberAttribute(        IsRequired=true, Order=2)]     public string Message {         get {             return this.MessageField;         }         set {             if ((object.ReferenceEquals(this.MessageField,                  value) != true)) {this.MessageField = value;                   this.RaisePropertyChanged("Message");             }         }     }     public event System.ComponentModel.       PropertyChangedEventHandler PropertyChanged;     protected void RaisePropertyChanged(       string propertyName) {System.ComponentModel.       PropertyChangedEventHandler propertyChanged =       this.PropertyChanged;         if ((propertyChanged != null)) {               propertyChanged(this, new System.              ComponentModel.PropertyChangedEventArgs               (propertyName));         }     } }  [System.CodeDom.Compiler.GeneratedCodeAttribute(   "System.ServiceModel", "3.0.0.0")]  [System.ServiceModel.ServiceContractAttribute(Namespace=   "http://www.thatindigogirl.com/samples/VS2008",   ConfigurationName="ServiceReference.IMessagingService")] public interface IMessagingService {      [System.ServiceModel.OperationContractAttribute(        Action="http://www.thatindigogirl.com/samples/        VS2008/IMessagingService/SendMessage", ReplyAction=        "http://www.thatindigogirl.com/samples/VS2008/        IMessagingService/SendMessageRespons" + "e")]     void SendMessage(WinClient.ServiceReference.       MessageInfo message); }  [System.CodeDom.Compiler.GeneratedCodeAttribute(    "System.ServiceModel", "3.0.0.0")] public interface IMessagingServiceChannel :   WinClient.ServiceReference.IMessagingService,    System.ServiceModel.IClientChannel { }  [System.Diagnostics.DebuggerStepThroughAttribute()]  [System.CodeDom.Compiler.GeneratedCodeAttribute(    "System.ServiceModel", "3.0.0.0")] public partial class MessagingServiceClient :   System.ServiceModel.ClientBase, WinClient.  ServiceReference.IMessagingService {     public MessagingServiceClient() {     }     public MessagingServiceClient(string       endpointConfigurationName) : base(       endpointConfigurationName) {     }     public MessagingServiceClient(string       endpointConfigurationName, string remoteAddress) :       base(endpointConfigurationName, remoteAddress) {     }     public MessagingServiceClient(string       endpointConfigurationName, System.ServiceModel.       EndpointAddress remoteAddress) :       base(endpointConfigurationName, remoteAddress) {     }     public MessagingServiceClient(System.ServiceModel.       Channels.Binding binding, System.ServiceModel.       EndpointAddress remoteAddress) : base(binding,       remoteAddress) {     }     public void SendMessage(WinClient.ServiceReference.       MessageInfo message) {         base.Channel.SendMessage(message);     } }}

End Listing One

Read more about:

Microsoft
Sign up for the ITPro Today newsletter
Stay on top of the IT universe with commentary, news analysis, how-to's, and tips delivered to your inbox daily.

You May Also Like