WCF and Proxies

In this final article in a five-part series on using the Windows Communication Foundation (WCF), you will learn how to create and use a proxy, how to programmatically configure a client, and more. It is excerpted from chapter one of Programming WCF Services, written by Juval Lowry (O'Reilly, 2007; ISBN: 0596526997). Copyright © 2007 O'Reilly Media, Inc. All rights reserved. Used with permission from the publisher. Available from booksellers or direct from O'Reilly Media.

Contributed by
Rating: 5 stars5 stars5 stars5 stars5 stars / 6
October 18, 2007
Rate this Article:
MEH MEH++


SEARCH ASP FREE
TOOLS YOU CAN USE

advertisement

Creating and Using the Proxy

The proxy class derives from the class ClientBase<T>, defined as:

  public abstract class ClientBase<T> : ICommunicationObject,IDisposable
  {
     protected ClientBase(string endpointName);
     protected ClientBase(Binding binding,EndpointAddress remoteAddress);
     public void Open();
     public void Close();
     protected T Channel
     {get;}
     //Additional members 
 
}

ClientBase<T>accepts a single generic type parameter identifying the service contract that this proxy encapsulates. TheChannel property ofClientBase<T>is of the type of that type parameter. The generated subclass ofClientBase<T>simply delegates toChannelthe method call (see Example 1-15).

To use the proxy, the client first needs to instantiate a proxy object and to provide the constructor with endpoint information: either the endpoint section name from the config file, or the endpoint address and binding objects if you’re not using a config file. The client can then use the proxy methods to call the service, and when the client is done, the client needs to close the proxy instance. For example, given the same definitions as in Examples 1-15 and 1-16, the client constructs the proxy, identifying the endpoint to use from the config file; invokes the method; and closes the proxy:

  MyContractClient proxy = new MyContractClient("MyEndpoint");
  proxy.MyMethod();
  proxy.Close();

If only one endpoint is defined in the client config file for the type of contract the proxy is using, then the client can omit the endpoint name from the proxy’s constructor:

  MyContractClient proxy = new MyContractClient();
 
proxy.MyMethod();
  proxy.Close();

However, if multiple endpoints are available for the same contract type then the proxy throws an exception.

Closing the proxy

It is a recommended best practice to always close the proxy when the client is done using it. You will see in Chapter 4 why the client needs to close the proxy in certain cases, because closing the proxy terminates the session with the service and closes the connection. 

Alternatively, you can use theDispose()method of the proxy to close it. The advantage of theDispose()method is that you can use theusingstatement to call it even in the face of exceptions:

  using(MyContractClient proxy = new MyContractClient())
  { 
    
proxy.MyMethod();
 
}

If the client is declaring the contract directly instead of the concrete proxy class, the client can either query for the presence of IDisposable:

  IMyContract proxy = new MyContractClient());
  proxy.MyMethod();
  IDisposable disposable = proxy as IDisposable;
  if(disposable != null)
  {
    
disposable.Dispose();
  }

or collapse the query inside theusingstatement:

  IMyContract proxy = new MyContractClient();
 
using(proxy as IDisposable
  {
     proxy.MyMethod();
  }

Call timeout

Each call made by a WCF client must complete within a configurable timeout. If for whatever reason the call duration exceeds the timeout, the call is aborted and the client gets a TimeoutException. The exact value of the timeout is a property of the binding, where the default timeout is one minute. To provide a different timeout, set the SendTimeout property of the abstract Binding base class:

  public abstract class Binding : ...
  {
     public TimeSpan SendTimeout
     {get;set;}
     //More members
  }

For example, when using theWSHttpBinding:

  <client>
     <endpoint
        ...
        binding = "wsHttpBinding"
        bindingConfiguration = "LongTimeout"
        ...
     />
  </client>
  <bindings>
    
<wsHttpBinding>
        <binding name = "LongTimeout" sendTimeout = "00:05:00"/>
     </wsHttpBinding>
  </bindings>

Programmatic Client Configuration

Instead of relying on a config file, the client can programmatically construct address and binding objects matching the service endpoint and provide them to the proxy constructor. There is no need to provide the contract, since that was provided in the form of the generic type parameter of the proxy. To represent the address, the client needs to instantiate an EndpointAddress class, defined as:

  public class EndpointAddress
  {
     public EndpointAddress(string uri);
     //More members
 
}

Example 1-20 demonstrates this technique, showing the code equivalent to Example 1-16 targeting the service in Example 1-9.

Example 1-20. Programmatic client configuration

Binding wsBinding = new WSHttpBinding(); EndpointAddress endpointAddress = new
                     
EndpointAddress("http://localhost:8000/MyService/");

MyContractClient proxy = new MyContractClient(wsBinding,endpointAddress);

proxy.MyMethod();
proxy.Close();

Similar to using a binding section in a config file, the client can programmatically configure the binding properties:

  WSHttpBinding wsBinding = new WSHttpBinding();
  wsBinding.SendTimeout = TimeSpan.FromMinutes(5);
  wsBinding.TransactionFlow = true;

  EndpointAddress endpointAddress = new
                       EndpointAddress("http://localhost:8000/MyService/");

  MyContractClient proxy = new MyContractClient(wsBinding,endpointAddress);
  proxy.MyMethod();
  proxy.Close();

Again, note the use of the concrete subclass ofBindingin order to access binding-specific properties such as the transaction flow.

Programmatic Versus Administrative Configuration

The two techniques shown so far for configuring both the client and service complement each other. Administrative configuration gives you the option to change major aspects of the service and the client post-deployment, without even the need to rebuild or redeploy. The major downside of administrative configuration is that it is not type-safe, and configuration errors will only be discovered at runtime.

Programmatic configuration is useful when the configuration decision is either completely dynamic—when it is taken at runtime based on the current input or conditions—or when the decision is static and never changes, in which case you might as well hardcode it. For example, if you are interested in hosting in-proc calls only, you might as well hardcode the use of theNetNamedPipeBindingand its configuration. However, by and large, most clients and services do resort to using a config file.

WCF Architecture

So far in this chapter, I’ve covered all that is required to set up and consume simple WCF services. However, as described in the rest of the book, WCF offers immensely valuable support for reliability, transactions, concurrency management, security, and instance activation, all of which rely on the WCF interception-based architecture.

Having the client interact with a proxy means that WCF is always present between the service and the client, intercepting the call and performing pre-and post-call processing. The interception starts when the proxy serializes the call stack frame to a message and sends the message down a chain of channels. The channelis merely an interceptor, whose purpose is to perform a specific task. Each client-side channel does pre-call processing of the message. The exact structure and composition of the chain depends mostly on the binding. For example, one of the channels may be responsible for encoding the message (binary, text, or MTOM), another for passing security call context, another for propagating the client transaction, another for managing the reliable session, another for encrypting the message body (if so configured), and so on. The last channel on the client side is the transport channel, which sends the message over the configured transport to the host.

On the host side, the message goes through a chain of channels as well, which perform host-side pre-call processing of the message. The first channel on the host side is the transport channel, which receives the message from the transport. Subsequent channels perform various tasks, such as decryption of the message body, decoding of the message, joining the propagated transaction, setting the security principal, managing the session, and activating the service instance. The last channel on the host side passes the message to the dispatcher. The dispatcher converts the message to a stack frame and calls the service instance. This sequence is depicted in Figure 1-11.


Figure 1-11.  The WCF architecture

The service has no way of knowing it was not called by a local client. In fact, it wascalled by a local client—the dispatcher. The interception both on the client and the service side ensures that the client and the service get the runtime environment they require to operate properly. The service instance executes the call and returns control to the dispatcher, which then converts the returned values and error information (if any) to a return message. The process is now reversed: the dispatcher passes the message through the host-side channels to perform post-call processing, such as managing the transaction, deactivating the instance, encoding the reply, encrypting it, and so on. The returned message goes to the transport channel, which sends it to the client-side channels for client-side post-call processing, which consists of tasks such as decryption, decoding, committing or aborting the transaction, and so on. The last channel passes the message to the proxy. The proxy converts the returned message to a stack frame and returns control to the client.

Most noteworthy is that almost all the points in the architecture provide hooks for extensibility—you can provide custom channels for proprietary interaction, custom behaviors for instance management, custom security behavior, and so on. In fact, the standard facilities that WCF offers are all implemented using the same extensibility model. You will see many examples and uses for extensibility throughout this book.

Host Architecture

It is also interesting to explore how the transition is made from a technology-neutral, service-oriented interaction to CLR interfaces and classes. The bridging is done via the host. Each .NET host process can have many app domains. Each app domain can have zero or more service host instances. However, each service host instance is dedicated to a particular service type. When you create a host instance, you are in effect registering that service host instance with all the endpoints for that type on the host machine that correspond to its base addresses. Each service host instance has zero or more contexts. The context is the innermost execution scope of the service instance. A context is associated with at most one service instance, meaning it could also be empty, without any service instance. This architecture is shown in Figure 1-12.


Figure 1-12.  The WCF host architecture

The WCF context is conceptually similar to the Enterprise Services context or the .NET context-bound object context.

It is the combined work of the service host and the context that exposes a native CLR type as a service. After the message is passed through the channels, the host maps that message to a new or existing context (and the object instance inside) and lets it process the call.

Working with Channels

You can use channels directly to invoke operations on the service without ever resorting to using a proxy class. The ChannelFactory<T> class (and its supporting types), shown in Example 1-21, enables you to create a proxy on the fly.

Example 1-21. TheChannelFactory<T> class

public class ContractDescription
{
   public Type ContractType
   {get;set;}
   //More members
}

public class ServiceEndpoint
{
   public ServiceEndpoint(ContractDescription contract,Binding binding,
                   
EndpointAddress address);
   public EndpointAddress Address
   {get;set;}
   public Binding Binding
   {get;set;}
   public ContractDescription Contract
   {get;}
   //More members
}

public abstract class ChannelFactory : ...
{
   public ServiceEndpoint Endpoint
   {get;}
   //More members
}
public class ChannelFactory<T> : ChannelFactory,...
{
  
public ChannelFactory(ServiceEndpoint endpoint);
   public ChannelFactory(string configurationName);
   public ChannelFactory(Binding binding,EndpointAddress endpointAddress);
   public static T CreateChannel(Binding binding,EndpointAddress endpointAddress);
   public T CreateChannel();
  
//More Members
}

You need to provide the constructor ofChannelFactory<T>with the endpoint—either the endpoint name from the client config file, or the binding and address objects, or aServiceEndpointobject. Next, use theCreateChannel()method to obtain a reference to the proxy and use its methods. Finally, close the proxy by either casting it toIDisposableand calling theDispose()method or toICommunicationObjectand calling theClose()method:

  ChannelFactory<IMyContract> factory = new ChannelFactory<IMyContract>();

  IMyContract proxy1 = factory.CreateChannel();
  using(proxy1 as IDisposable)
  {
    
proxy1.MyMethod();
  }

  IMyContract proxy2 = factory.CreateChannel();
  proxy2.MyMethod();
  ICommunicationObject channel = proxy2 as ICommunicationObject;
  Debug.Assert(channel != null);
  channel.Close();

You can also use the shorthand staticCreateChannel()method to create a proxy given a binding and an address, without directly constructing an instance ofChannelFactory<T>:

  Binding binding = new NetTcpBinding();
  EndpointAddress address = new EndpointAddress("net.tcp://localhost:8000");

  IMyContract proxy = ChannelFactory<IMyContract>.CreateChannel(binding,address);
  using(proxy as IDisposable)
  {
    
proxy1.MyMethod();
  }

The InProcFactory Class

To demonstrate the power of ChannelFactory<T>, consider my static helper class InProcFactory, defined as:

  public static class InProcFactory
  {
     public static I CreateInstance<S,I>() where I : class
                             
where S : I;
     public static void CloseProxy<I>(I instance) where I : class;
     //More members
 
}

InProcFactoryis designed to streamline and automate in-proc hosting. TheCreateInstance()method takes two generic type parameters: the type of the service S and the type of the supported contractI.CreateInstance()constrainsSto derive fromI. UsingInProcFactoryis straightforward:

  IMyContract proxy = InProcFactory.CreateInstance<MyService,IMyContract>();

  proxy.MyMethod();

  InProcFactory.CloseProxy(proxy);

It literally takes a service class and hoists it up as a WCF service. It is as close as you can get in WCF to the old Win32 call ofLoadLibrary().

Implementing InProcFactory<T>

All in-proc calls should use named pipes, and should also flow all transactions. You can use programmatic configuration to automate the configurations of both the client and the service, and use ChannelFactory<T> to avoid the need for a proxy. Example 1-22 shows the implementation of InProcFactory with some of the code removed for brevity.

Example 1-22. The InProcFactory class

public static class InProcFactory
{
   struct HostRecord
   {
     
public HostRecord(ServiceHost host,string address)
     
{
         Host = host;
         Address = address;
     
}
      public readonly ServiceHost Host;
      public readonly string Address;
   
}
   static readonly Uri BaseAddress = new Uri("net.pipe://localhost/");
   static readonly Binding NamedPipeBinding;
   static Dictionary<Type,HostRecord> m_Hosts = new Dictionary<Type,HostRecord>();

   static InProcFactory()
   {
      NetNamedPipeBinding binding = new NetNamedPipeBinding();
      binding.TransactionFlow = true;
      NamedPipeBinding = binding;
      AppDomain.CurrentDomain.ProcessExit += delegate
                                      
{
                             foreach(KeyValuePair<Type,HostRecord> pair in m_Hosts)
             {
               pair.Value.Host.Close();
             }
        
};
   }
   public static I CreateInstance<S,I>() where I : class
                                 where S : I
   {
     HostRecord hostRecord = GetHostRecord<S,I>();
      return ChannelFactory<I>.CreateChannel(NamedPipeBinding,
   new EndpointAddress(hostRecord.Address));
   }
   static HostRecord GetHostRecord<S,I>() where I : class
                                
where S : I
   {
      HostRecord hostRecord;
      if(m_Hosts.ContainsKey(typeof(S)))
      {
        
hostRecord = m_Hosts[typeof(S)];
      }
      else
      {
        
ServiceHost host = new ServiceHost(typeof(S), BaseAddress);
         string address = BaseAddress.ToString() + Guid.NewGuid().ToString();
         hostRecord = new HostRecord(host,address);
         m_Hosts.Add(typeof(S),hostRecord);
         host.AddServiceEndpoint(typeof(I),NamedPipeBinding,address);
         host.Open();
     
}
     
return hostRecord;
   }
   public static void CloseProxy<I>(I instance) where I : class
   {
     
ICommunicationObject proxy = instance as ICommunicationObject;
      Debug.Assert(proxy != null);
      proxy.Close();
   }
}

The main challenge facingInProcFactoryis thatCreateInstance()can be called to instantiate services of every type. For every service type, there should be a single matching host (an instance ofServiceHost). Allocating a host instance for each call is not a good idea. The problem is what shouldCreateInstance()do when it is asked to instantiate a second object of the same type:

  IMyContract proxy1 = InProcFactory.CreateInstance<MyService,IMyContract>();
  IMyContract proxy2 = InProcFactory.CreateInstance<MyService,IMyContract>();

The solution is to internally manage a dictionary that maps service types to a particular host instance. WhenCreateInstance()is called to create an instance of a particular type, it looks in the dictionary, using a helper method calledGetHostRecord(), which creates the host only if the dictionary does not already contain the service type. If it needs to create a host,GetHostRecord()programmatically adds to that host an endpoint, using a newGUIDas a unique pipe name.CreateInstance()then grabs the address of the endpoint from the host record and usesChannelFactory<T>to create the proxy. In its static constructor, which is called upon the first use of the class,InProcFactorysubscribes to the process exit event, using an anonymous method to close all hosts when the process shuts down. Finally, to help the clients close the proxy,InProcFactoryprovides theCloseProxy()method, which queries the proxy toICommunicationObjectand closes it.

Reliability

WCF and other service-oriented technologies make a distinction between transport reliability and message reliability. Transport reliability(such as the one offered by TCP) offers point-to-point guaranteed delivery at the network packet level, as well as guarantees the order of the packets. Transport reliability is not resilient to dropping network connections and a variety of other communication problems.

Message reliability,as the name implies, deals with reliability at the message level independent of how many packets are required to deliver the message. Message reliability provides for end-to-end guaranteed delivery and order of messages, regardless of how many intermediaries are involved, and how many network hops are required to deliver the message from the client to the service. Message reliability is based on an industry standard for reliable message-based communication that maintains a session at the transport level. It offers retries in case of transport failures such as dropping a wireless connection; it automatically deals with congestion, message buffering, and flow control; and it can adjust the number of messages accordingly. Message reliability also deals with managing the connection itself via connection verification and cleanup when no longer needed.

Binding and Reliability

In WCF, reliability is controlled and configured in the binding. A particular binding can support or not support reliable messaging, and if supported, it can be enabled or disabled. Which binding supports which reliability value is driven by the target scenario for that particular binding. Table 1-2 summarizes the relationship between binding, reliability, and ordered delivery and their respective default values.

Table 1-2. Reliability and binding

Name Supports reliability Default reliability Supports ordered Default ordered
BasicHttpBinding No N/A No N/A
NetTcpBinding Yes Off Yes On
NetPeerTcpBinding No N/A No N/A
NetNamedPipeBinding No N/A (On) Yes N/A (On)
WSHttpBinding Yes Off Yes On
WSFederationHttpBinding Yes Off Yes On
WSDualHttpBinding Yes On Yes On

Table 1-2. Reliability and binding (continued)

Name

Supports reliability

Default reliability

Supports ordered

Default ordered

NetMsmqBinding

No

N/A

No

N/A

MsmqIntegrationBinding

No

N/A

No

N/A

 

 

 

 

 

 

Reliability is not supported by theBasicHttpBinding,NetPeerTcpBinding, and the two MSMQ bindings,NetMsmqBindingandMsmqIntegrationBinding. The reason is that theBasicHttpBindingis oriented toward the legacy ASMX web services world, which does not have reliability.NetPeerTcpBindingis designed for broadcast scenarios. The MSMQ bindings are for disconnected calls, where no transport session is possible anyway.

Reliability is always enabled onWSDualHttpBindingto keep the callback channel to the client alive even over HTTP.

Reliability is disabled by default but can be enabled on theNetTcpBindingand the various WS bindings. Finally, theNetNamedPipeBindingis considered inherently reliable because it always has exactly one hop from the client to the service.

Ordered Messages

Message reliability also provides ordered delivery assurance, allowing messages to be executed in the order they were sent, not in the order in which they were delivered. In addition, it guarantees that messages are delivered exactly once.

WCF lets you enable reliability but not ordered delivery, in which case messages are delivered in the order in which they were received. The default for all bindings that support reliability is that when reliability is enabled, ordered delivery is enabled as well.

Configuring Reliability

You can configure reliability (and ordered delivery) both programmatically and administratively. When you enable reliability, you must do so on both the client and the service host sides, otherwise the client will not be able communicate with the service. You can only configure reliability for the bindings that support it. Example 1-23 shows the service-side config file that uses a binding configuration section to enable reliability when using the TCP binding.

Example 1-23. Enabling reliability with the TCP binding

<system.serviceModel>
   <services>
      <service name = "MyService">
         <endpoint
            address = "net.tcp://localhost:8000/MyService"
           
binding  = "netTcpBinding"
           
bindingConfiguration = "ReliableTCP"
          
contract = "IMyContract"
         />
      </service>
   </services>
   <bindings>
     
<netTcpBinding>
         <binding name = "ReliableTCP">
           <reliableSession enabled = "true"/>
         </binding>
      </netTcpBinding>
   </bindings>
</system.serviceModel>

When it comes to programmatic configuration, the TCP and the WS bindings offer slightly different properties for configuring reliability. For example, theNetTcpBindingbinding accepts a Boolean construction parameter for enabling reliability:

  public class NetTcpBinding : Binding,...
  {
     public NetTcpBinding(...,bool reliableSessionEnabled);
     //More members
 
}

You can only enable reliability at construction time, so when you set reliability programmatically, you need to construct the binding as reliable:

  Binding reliableTcpBinding = new NetTcpBinding(...,true);

NetTcpBindingalso offers the read-onlyReliableSessionclass, letting you retrieve the reliability status:

  public class ReliableSession
 
{
     public TimeSpan InactivityTimeout
     {get;set;}
     public bool Ordered
     {get;set;}
     //More members
 
}
  public class OptionalReliableSession : ReliableSession
  {
    
public bool Enabled
     {get;set;}
     //More members 
  
}
  public class NetTcpBinding : Binding,...
  {
    
public OptionalReliableSession ReliableSession
     {get;}
     //More members
 
}

Requiring Ordered Delivery

In theory, the service code and the contract definition should be independent of the binding used and of its properties. The service should not care about the binding, and nothing in service code pertains to the binding used. The service should be able to work with any aspect of the configured binding. In practice, the service implementation or the contract itself may depend on ordered delivery of the messages. To enable the contract or service developer to constrain the allowed bindings, WCF defines the DeliveryRequirementsAttribute:

  [AttributeUsage(AttributeTargets.Class|AttributeTargets.Interface
                
AllowMultiple = true)]
  public sealed class DeliveryRequirementsAttribute : Attribute,...
  {
    
public Type TargetContract
    
{get;set;}
    
public bool RequireOrderedDelivery
     {get;set;}

     //More members
  }

TheDeliveryRequirementsattribute can be applied at the service level, affecting all endpoints of the service, or only at the endpoints that expose a particular contract. When applied at the service level, it means that requiring ordered delivery is an implementation decision. The attribute can also be used at the contract level, affecting all services that support that contract. When applied at the contract level, it means that requiring ordered delivery is a design decision. Enforcing the constraint is done at the service load time. If an endpoint has a binding that does not support reliability, or supports reliability and has reliability disabled, or has reliability enabled yet ordered delivery is disabled, loading the service will fail withInvalidOperationException.

The named pipe binding satisfies the ordered delivery constraint.

For example, to demand that all endpoints of the service, regardless of contracts, have ordered delivery enabled, apply the attribute directly on the service class:

  [DeliveryRequirements(RequireOrderedDelivery = true)]
  class MyService : IMyContract,IMyOtherContract
  {...}

By setting theTargetContractproperty, you can demand that only endpoints of the service that support that contract be constrained to have reliable ordered delivery:

  [DeliveryRequirements(TargetContract = typeof(IMyContract)
                      RequireOrderedDelivery = true)]
  class MyService : IMyContract,IMyOtherContract
  {...}

By applying theDeliveryRequirementsattribute on the contract interface, you place the constraint on all services that support it:

  [DeliveryRequirements(RequireOrderedDelivery = true)]
  [ServiceContract]
  interface IMyContract
  {...}

  class MyService : IMyContract
  {...}

  class MyOtherService : IMyContract
  {...}

The default of theRequireOrderedDeliveryisfalse, so merely applying the attribute has no effect. For example, these statements are equivalent:

  [ServiceContract]
  interface IMyContract
  {...}

  [DeliveryRequirements]
  [ServiceContract]
  interface IMyContract
  {...}

  [DeliveryRequirements(RequireOrderedDelivery = false)]
  [ServiceContract]
  interface IMyContract
  {...}

blog comments powered by Disqus
WINDOWS SCRIPTING ARTICLES

- More Windows Scripting Workarounds from Nilpo
- Overloading Methods and More in VBScript
- Improving MFC for Windows Vista
- Regular Expressions in VBScript
- Working with Dates in WMI
- Completing Calendars with VBScript Date Func...
- Building Calendars with VBScript Date Functi...
- Working With Dates and Times in VBScript
- Designing WCF DataContract Classes Using the...
- Understanding Dates and Times in VBScript
- Working With Arrays in VBScript
- Compressed Folders in WSH
- Using .NET Interops in VBScript
- Nilpo`s Scripting Secrets, Vol I
- Database operations using Silverlight 2.0 WC...

ASP Web Hosting ASP.Net Web Hosting Windows Web Hosting
 
 
 

ASP Free Forums 
 RSS  Tutorials RSS
 RSS  Forums RSS
 RSS  All Feeds
Site Map 
Request Media Kit
Write For Us Get Paid 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Privacy Policy 
Support 


© 2003-2012 by Developer Shed. All rights reserved. DS Cluster 9 - Follow our Sitemap
Most Popular Topics
All ASP.Net Tutorials