Section 9.9. Response Service


9.9. Response Service

The programming model of queued calls described so far was one-sided: the client posted a one-way message to a queue, and the service processed that message. This model is sufficient when the queued operations are one-way calls by design. However, the queued service may need to report back to its client on the result of the invocation, returned results, and even errors. By default, however, this is not possible. WCF equates queued calls with one-way calls, which inherently forbids any such response. In addition, queued services (and their clients) are potentially disconnected. If the client posts a queued call to a disconnected service, when the service finally gets the messages and processes them, there may not be a client to return the values to because the client may be long gone. The solution is to have the service report back to a client-provided queued service. I call such a service a response service.[*] Figure 9-12 shows the architecture of such a solution.

[*] I first published my techniques for a response service in MSDN Magazine, February 2007.

Figure 9-12. A response service


The response service is just another queued service in the system. The response service may be disconnected toward the client as well and may be hosted in a separate process or a separate machine, or it can share the client's process. If the response service shares the client's process, then when the client is launched the response service will start processing the queued responses. Having the response service in a separate process (or even machine) from the client's helps to further decouple lifeline-wise the response service from the client or clients that use it.

Not all queued services require a response service, and as you will see, responses add complexity to the system. Be pragmatic, and use a response service only where appropriate; that is, where it adds the most value.


9.9.1. Designing a Response Service Contract

Similar to the use of any WCF service, the client and the service need to agree beforehand on the response contract and what it will be used for, such as for returned values and error information or just returned values. Note that you can also split the response service into two services, and have one response service for results and another for faults and errors. For example, consider the ICalculator contract implemented by the queued MyCalculator service:

 [ServiceContract] interface ICalculator {    [OperationContract(IsOneWay = true)]    void Add(int number1,int number2);    //More operations } [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyCalculator : ICalculator {...} 

The MyCalculator service is required to respond back to its client with the result of the calculation and report on any errors. The result of the calculation is an integer, and the error is in the form of the ExceptionDetail data contract presented in Chapter 6. For the response service, you the ICalculatorResponse contract could be defined as:

 [ServiceContract] interface ICalculatorResponse {    [OperationContract(IsOneWay = true)]    void OnAddCompleted(int result,ExceptionDetail error); } 

The response service supporting ICalculatorResponse needs to examine the returned error information, notify the client application, the user, or the application administrator on the method completion, and make the results available to the interested parties. Example 9-21 shows a simple response service that supports ICalculatorResponse.

Example 9-21. A simple response service

 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyCalculatorResponse : ICalculatorResponse {    public void OnAddCompleted(int result,ExceptionDetail error)    {       MessageBox.Show("result =  " + result,"MyCalculatorResponse");       if(error != null)       {          //Handle error       }    } } 

9.9.1.1. Response address and method ID

There are two immediate problems with the implementation of both MyCalculator and MyCalculatorResponse. The first is that the same response service could be used to handle the response (or completion) of multiple calls on multiple queued services, and yet, as listed in Example 9-21, MyCalculatorResponse (and more importantly the clients it serves) has no way of distinguishing between responses. The solution for that is to have the client that issued the original queued call tag the call by associating it with some unique ID, or at least an ID that is unique across that client's application. The queued service MyCalculator needs to pass that ID to the response service MyCalculatorResponse, so that it can apply its custom logic regarding that ID. The second problem is discovering the address of the response service by the queued service. Unlike duplex callbacks, there is no built-in support in WCF for passing the response service reference to the service, so the queued service needs to construct a proxy to the response service and invoke the operations of the response contract. While the response contract is decided upon at design time, and the binding is always NetMsmqBinding (or variations of it as configured), the queued service lacks the address of the response service to be able to respond. While you could place that address in the service host config file (in a client section) such a course of action is to be avoided. The main reason is that the same queued service could be called by multiple clients, each with its own dedicated response service and address. One possible solution is to explicitly pass both the client-managed ID and the desired response service address as parameters to every operation on the queued service contract:

 [ServiceContract] interface ICalculator {    [OperationContract(IsOneWay = true)]    void Add(int number1,int number2,string responseAddress,string methodId); } 

Much the same way, the queued service could explicitly pass the method ID to the response service as a parameter to every operation on the queued response contract:

 [ServiceContract] interface ICalculatorResponse {    [OperationContract(IsOneWay = true)]    void OnAddCompleted(int result,ExceptionDetail error,string methodId); } 

9.9.1.2. Using message headers

While passing the address and the ID as explicit parameters would work, it does distort the original contract, and introduces plumbing-level parameters along business-level parameters in the same operation. A better solution is to have the client store the response address and operation ID in the outgoing message headers of the call. Using the message headers this way is a general-purpose technique for passing out-of-band information to the service, information that is otherwise not present in the service contract.

The operation context offers collections of incoming and outgoing headers, available with the IncomingMessageHeaders and OutgoingMessageHeaders properties:

 public sealed class OperationContext : ... {    public MessageHeaders IncomingMessageHeaders    {get;}    public MessageHeaders OutgoingMessageHeaders    {get;}    //More members } 

Each collection is of the type MessageHeadersa collection of MessageHeader objects:

 public sealed class MessageHeaders : IEnumerable<...>,... {    public void Add(MessageHeader header);    public T GetHeader<T>(int index);    public T GetHeader<T>(string name,string ns);    //More members } 

The class MessageHeader is not intended for application developers to interact with directly. Instead, use the MessageHeader<T> class, which provides type-safe and easy conversion from a CLR type to a message header:

 public abstract class MessageHeader : ... {...} public class MessageHeader<T> {    public MessageHeader( );    public MessageHeader(T content);    public T Content    {get;set;}    public MessageHeader GetUntypedHeader(string name,string ns);    //More members } 

As the type parameter for MessageHeader<T> you can use any serializable or data contract type. You can construct a MessageHeader<T> around a CLR type, and then use the GetUntypedHeader( ) method to convert it to a MessageHeader and store it in the outgoing headers. GetUntypedHeader( ) requires you to provide it with the generic type parameter name and namespace. The name and namespace will be used later on to look up the header from the header collection. You perform the lookup via the GetHeader<T>( ) method of MessageHeaders. Calling GetHeader<T>( ) obtains the value of the type parameter of the MessageHeader<T> used.

9.9.1.3. The ResponseContext class

Since the client needs to pass in the message headers both the address and the method ID, a single primitive type parameter will not do. Instead, use my ResponseContext class, defined in Example 9-22.

Example 9-22. The ResponseContext class

 namespace ServiceModelEx {    [DataContract]    public class ResponseContext    {       [DataMember]       public readonly string ResponseAddress;       [DataMember]       public readonly string FaultAddress;       [DataMember]       public readonly string MethodId;       public ResponseContext(string responseAddress,string methodId) :                                                 this(responseAddress,methodId,null)       {}       public ResponseContext(ResponseContext responseContext) :                      this(responseContext.ResponseAddress,responseContext.MethodId,                                                       responseContext.FaultAddress)       {}       public ResponseContext(string responseAddress) : this(responseAddress,                                                          Guid.NewGuid().ToString( ))       {}       public ResponseContext(string responseAddress,string methodId,                              string faultAddress)       {          ResponseAddress = responseAddress;          MethodId = methodId;          FaultAddress = faultAddress;       }       //More members    } } 

ResponseContext provides a place to store both the response address and the ID, and, in addition, if the client would like to use a separate response service for faults, ResponseContext also provides a field for the fault response service address. This chapter makes no use of that feature. The client is responsible for constructing an instance of ResponseContext with a unique ID. While the client can supply that ID as a construction parameter, the client could also use the constructor of ResponseContext, which takes just the response address, and have that constructor generate a GUID for the ID.

9.9.2. Client-Side Programming

The client can provide an ID for each method call, even when dealing with a sessionful queued service, by using a different instance of ResponseContext for each call. The client needs to store an instance of ResponseContext in the outgoing message headers. In addition, the client must do so in new operation context, and the client cannot use its existing operation context. This requirement is true for services and non-service clients alike. WCF enables a client to adopt a new operation context for the current thread with the OperationContextScope class, defined as:

 public sealed class OperationContextScope : IDisposable {    public OperationContextScope(IContextChannel channel);    public OperationContextScope(OperationContext context);    public void Dispose( ); } 

OperationContextScope is a general technique for spinning a new context when the one you have is inadequate. The constructor of OperationContextScope replaces the current thread's operation context with the new context. Calling Dispose( ) on the OperationContextScope instance restores the old context (even if it was null). If you do not call Dispose( ), that may damage other objects on the same thread that expect the previous context. As a result, OperationContextScope is designed to be used inside a using statement and provide only a scope of code with a new operation context, even in the face of exceptions; hence its name:

 using(OperationContextScope scope = new OperationContextScope(...)) {    //Do work with new context    ... }//Restore previous context here 

When constructing a new OperationContextScope instance, you provide its constructor with the inner channel of the proxy used for the call. The client needs to instantiate a new ResponseContext object with the response address and ID, add that ResponseContext to a message header, create a new OperationContextScope, add that header to the outgoing message headers of the new context, and call the proxy. Example 9-23 shows these steps.

Example 9-23. Client-side programming with a response service

 string methodId = GenerateMethodId( ); string responseQueue = "net.msmq://localhost/private/MyCalculatorResponseQueue"; ResponseContext responseContext = new ResponseContext(responseQueue,methodId); MessageHeader<ResponseContext> responseHeader =                                new MessageHeader<ResponseContext>(responseContext); CalculatorClient proxy = new CalculatorClient( ); using(OperationContextScope contextScope =                                      new OperationContextScope(proxy.InnerChannel)) {    OperationContext.Current.OutgoingMessageHeaders.Add(               responseHeader.GetUntypedHeader("ResponseContext","ServiceModelEx"));    proxy.Add(2,3); } proxy.Close( ); //Helper method string GenerateMethodId( ) {...} 

In Example 9-23, the client uses the helper method GenerateMethodId( ) to generate a unique ID. It then creates a new instance of ResponseContext using the response address and the ID for construction parameters. The client uses MessageHeader<ResponseContext> to wrap the ResponseContext instance with a message header. The client constructs a new proxy to the queued service and uses its inner channel as a construction parameter for a new OperationContextScope. The scope is encased in a using statement to automatically restore the previous operation context. Inside the new context scope, the client accesses its new operation context, and obtains from it the OutgoingMessageHeaders collection. The client calls the GetUntypedHeader( ) method of the typed header to convert it to a raw MessageHeader while providing the name and namespace of the response context, and adds that header to the outgoing headers collection. The client calls the proxy, disposes of the operation context scope, and closes the proxy. The queued message now contains the out-of-band response address and ID. In Example 9-23 you can easily use a channel factory instead of a proxy class, by querying the returned proxy for the context channel to seed the operation context scope:

 ChannelFactory<ICalculator> factory = new ChannelFactory<ICalculator>(""); ICalculator proxy = factory.CreateChannel( ); using(OperationContextScope contextScope =                                new OperationContextScope(proxy as IContextChannel)) {...} 

9.9.3. Service-Side Programming

The queued service accesses its incoming message headers collection and reads from it the response address as well as the method ID. The service needs the address so that it can construct the proxy to the response service, and the ID so that it can provide that ID to the response service. The service usually has no direct use for the ID. The service can use the same technique as the client to pass the method ID to the response service; namely, instead of explicit parameters, use the outgoing message headers to pass the ID out-of-band to the response service. Much like the client, the service too must use a new operation context via OperationContextScope in order to be able to modify the outgoing headers collection. The service can programmatically construct a proxy to the response service and provide the proxy with the response address and an instance of NetMsmqBinding. The service can even read the binding settings from the config file. Example 9-24 shows these steps.

Example 9-24. Service-side programming with a response service

 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyCalculator : ICalculator {    [OperationBehavior(TransactionScopeRequired = true)]    public void Add(int number1,int number2)    {       int result = 0;       ExceptionDetail error = null;       try       {          result = number1 + number2;       }       //Don't re-throw       catch(Exception exception)       {          error = new ExceptionDetail(exception);       }       finally       {          ResponseContext responseContext =                               OperationContext.Current.IncomingMessageHeaders.                     GetHeader<ResponseContext>("ResponseContext","ServiceModelEx");          EndpointAddress responseAddress =                               new EndpointAddress(responseContext.ResponseAddress);          MessageHeader<ResponseContext> responseHeader =                                new MessageHeader<ResponseContext>(responseContext);          NetMsmqBinding binding = new NetMsmqBinding( );          CalculatorResponseClient proxy =                              new CalculatorResponseClient(binding,responseAddress);          using(OperationContextScope scope =                                      new OperationContextScope(proxy.InnerChannel))          {             OperationContext.Current.OutgoingMessageHeaders.Add(               responseHeader.GetUntypedHeader("ResponseContext","ServiceModelEx"));             proxy.OnAddCompleted(result,error);          }          proxy.Close( );       }    } } 

In Example 9-24, the service catches all exceptions thrown by the business logic operation, and wraps that exception with an ExceptionDetail object. The service does not rethrow the exception. As you will see later on in the context of transactions and response services, rethrowing would also cancel the response. Moreover, when using a response service, being able to respond in case of an error is a much better strategy than relying on the WCF playback error-handling.

In the finally statement, regardless of exceptions, the service responds. It accesses its own operation context collection of incoming headers and using the GetHeader<T>( ) method, it extracts the client-provided response context. Note the use of the name and namespace to look up the response context from the incoming headers collection. The service then constructs a proxy to the response service using the address from the response context and a new instance of NetMsmqBinding. The service uses the proxy's inner channel to seed a new OperationContextScope. Inside the new scope, the service adds to the new context's outgoing headers the same response context it got in (although admittedly it could have just added the ID), and calls the response service proxy, in effect queuing the response. The service then disposes of the context scope and closes the proxy. Sending the entire response context (not just the ID) to the response service is beneficial if the response service has any use for the fault response address.

9.9.4. Response Service-Side Programming

The response service accesses its incoming message headers collection, reads from it the method ID, and responds accordingly. Example 9-25 demonstrates a possible implementation of such a response service.

Example 9-25. Implementing a response service

 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyCalculatorResponse : ICalculatorResponse {    public static event GenericEventHandler<string,int> AddCompleted = delegate{};    public static event GenericEventHandler<string> AddError = delegate{};    [OperationBehavior(TransactionScopeRequired = true)]    public void OnAddCompleted(int result,ExceptionDetail error)    {       ResponseContext responseContext =                                    OperationContext.Current.IncomingMessageHeaders.                     GetHeader<ResponseContext>("ResponseContext","ServiceModelEx");       string methodId = responseContext.MethodId;       if(error == null)       {          AddCompleted(methodId,result);       }       else       {          AddError(methodId);       }    } } 

The response service in Example 9-25 provides two public static events, one for completion notification and the other for error notification. The response service invokes these delegates to notify subscribers about completion and errors, respectively. The response service accesses its operation-context incoming headers, and just like the queued service, it extracts out of them the response context and from that the method ID. The response service then invokes the completion delegate, providing the result of the operation and its ID. In the case of an exception, the response service invokes the fault delegate, passing only the method ID.

A queued response service is not limited to use only with a queued service. You can use the same technique to pass the address and method ID to a connected service and have that service respond to a client-provided queued response service.


9.9.5. Streamlining the Response Service

While the technique shown so far for implementing and consuming a response service certainly works, it is problematic. The problem with the techniques shown in Examples 9-23, 9-24, and 9-25 is that what was supposed to be business-logic service or client code is inundated with low-level WCF programming. Fortunately, it is possible to streamline and encapsulate this technique in helper classes to the point that each participating party can just interact with the other without the plumbing in the open.

The first act is to encapsulate the interaction with the raw message headers by adding the static Current property to ResponseContext:

 [DataContract] public class ResponseContext {    public static ResponseContext Current    {get;set;}    //Rest of the members } 

The Current property is listed in Example 9-26.

Example 9-26. The ResponseContext.Current property

 [DataContract] public class ResponseContext {    public static ResponseContext Current    {       get       {          OperationContext context = OperationContext.Current;          if(context == null)          {             return null;          }          try          {             return context.IncomingMessageHeaders.                     GetHeader<ResponseContext>("ResponseContext","ServiceModelEx");          }          catch          {             return null;          }       }       set       {          OperationContext context = OperationContext.Current;          Debug.Assert(context != null);          //Having multiple ResponseContext headers is an error          bool headerExists = false;          try          {             context.OutgoingMessageHeaders.GetHeader<ResponseContext>                                               ("ResponseContext","ServiceModelEx");             headerExists = true;          }          catch(MessageHeaderException exception)          {           Debug.Assert(exception.Message == "There is not a header with name                                              ResponseContext and namespace                                              ServiceModelEx in the message.");          }          if(headerExists)          {             throw new InvalidOperationException("A header with name ResponseContext                                                  and namespace ServiceModelEx                                                  already exists in the message.");          }          MessageHeader<ResponseContext> responseHeader                                        = new MessageHeader<ResponseContext>(value);          context.OutgoingMessageHeaders.Add              (responseHeader.GetUntypedHeader("ResponseContext","ServiceModelEx"));       }    }    //Rest same as Example 9-22. } 

The get accessor of Current returns null if the incoming headers do not contain the response context. This enables the queued service to verify if there is anyone to respond to:

 if(ResponseContext.Current != null) {    //Respond here } 

Note that the set accessor of Current verifies that no other ResponseContext exists in the outgoing headers collection of the current operation context.

9.9.5.1. Streamlining the client

To streamline and automate the client's work, you need a proxy base class that encapsulates the response service setup steps of Example 9-23. Unlike with duplex callback, WCF does not provide such a proxy class, so you have to handcraft one. To ease that task I provide the ResponseClientBase<T>, defined as:

 public abstract class ResponseClientBase<T> : ClientBase<T> where T : class {    public readonly string ResponseAddress;    public ResponseClientBase(string responseAddress);    public ResponseClientBase(string responseAddress,string endpointName);    public ResponseClientBase(string responseAddress,                              NetMsmqBinding binding,EndpointAddress remoteAddress);    /* More constructors */    protected string Enqueue(string operation,params object[] args);    protected virtual string GenerateMethodId( ); } 

To use ResponseClientBase<T>, derive a concrete class out of it, and provide for the type parameter the queued contract type. Unlike a normal proxy, do not have the subclass also derive from the contract. Instead, provide a similar set of methods that all return a string for the method ID, not void (this is why you cannot derive from the contract, because the operations on the contract do not return anythingthey are all one-way). For example, using this queued service contract:

 [ServiceContract] interface ICalculator {    [OperationContract(IsOneWay = true)]    void Add(int number1,int number2);    //More operations } 

Example 9-27 shows the matching response service aware proxy.

Example 9-27. Deriving from ResponseClientBase<T>

 class CalculatorClient : ResponseClientBase<ICalculator> {    public CalculatorClient(string responseAddress) : base(responseAddress)    {}    public CalculatorClient(string responseAddress,string endpointName) :                                                  base(responseAddress,endpointName)    {}    public CalculatorClient(string responseAddress,                               NetMsmqBinding binding,EndpointAddress remoteAddress)                                       : base(responseAddress,binding,remoteAddress)    {}    /* More constructors */    public string Add(int number1,int number2)    {       return Enqueue("Add",number1,number2);    } } 

Using ResponseClientBase<T>, Example 9-23 is reduced to:

 string responseAddress = "net.msmq://localhost/private/MyCalculatorResponseQueue"; CalculatorClient proxy = new CalculatorClient(responseAddress); string methodId = proxy.Add(2,3); proxy.Close( ); 

The virtual method GenerateMethodId( ) of ResponseClientBase<T> uses a GUID for a method ID. Your subclass of ResponseClientBase<T> can override it and provide any other unique string, such as a static incremented integer:

 class CalculatorClient : ResponseClientBase<ICalculator> {    static int m_MethodId = 123;    protected override string GenerateMethodId( )    {       lock(typeof(CalculatorClient))       {          int id = ++m_MethodId;          return id.ToString( );       }    }    //Rest of the implementation } 

Example 9-28 shows the implementation of ResponseClientBase<T>.

Example 9-28. Implementing ResponseClientBase<T>

 public class ResponseClientBase<T> : ClientBase<T> where T : class {    public readonly string ResponseAddress;    public ResponseClientBase(string responseAddress)    {       ResponseAddress = responseAddress;       QueuedServiceHelper.VerifyQueue(Endpoint);       Debug.Assert(Endpoint.Binding is NetMsmqBinding);    }    public ResponseClientBase(string responseAddress,string endpointName)                                                                : base(endpointName)    {       ResponseAddress = responseAddress;       QueuedServiceHelper.VerifyQueue(Endpoint);       Debug.Assert(Endpoint.Binding is NetMsmqBinding);    }    public ResponseClientBase(string responseAddress,                              NetMsmqBinding binding,EndpointAddress remoteAddress)                                                       : base(binding,remoteAddress)    {       ResponseAddress = responseAddress;       QueuedServiceHelper.VerifyQueue(Endpoint);    }    protected string Enqueue(string operation,params object[] args)    {       using(OperationContextScope contextScope =                                            new OperationContextScope(InnerChannel))       {          string methodId = GenerateMethodId( );          ResponseContext responseContext =                                      new ResponseContext(ResponseAddress,methodId);          ResponseContext.Current = responseContext;          Type contract = typeof(T);          //Does not support contract hierarchy or overloading          MethodInfo methodInfo = contract.GetMethod(operation);          methodInfo.Invoke(Channel,args);          return responseContext.MethodId;       }    }    protected virtual string GenerateMethodId( )    {       return Guid.NewGuid().ToString( );    } } 

The constructors of ResponseClientBase<T> accept the response address and the regular proxy parameters such as endpoint name, address, and binding. The constructors store the response address in a read-only public field. ResponseClientBase<T> derived from a regular ClientBase<T>, and so all constructors delegate to their respective base constructors. In addition, the constructors use my QueuedServiceHelper.VerifyQueue( ) method to verify that the queue (and the DLQ) exists and create it if necessary. The constructors also verify that the binding used for the service endpoint is NetMsmqBinding, because this proxy can only be used with queued calls. The heart of ResponseClientBase<T> is the Enqueue( ) method. Enqueue( ) accepts the name of the operation to invoke (actually queue a message to) and the operation parameters. Enqueue( ) creates a new operation context scope, generates a new method ID, and stores the ID and the response address in a ResponseContext. Then, it assigns the ResponseContext to the outgoing message headers by setting ResponseContext.Current. Enqueue( ) then uses reflection to invoke the provided operation name. Due to the use of reflection and late binding, ResponseClientBase<T> does not support contract hierarchy or overloaded operations. For those, you need manual coding such as in Example 9-23.

If you want to use a queued response service with a connected service (that does not use NetMsmqBinding), rework ResponseClientBase<T> so that it only uses Binding, rename Enqueue( ) to Invoke( ), and avoid asserting the use of a queued call.


9.9.5.2. Streamlining the queued service

To streamline and automate the work required by the service to extract the response parameters from the headers and respond, I created the ResponseScope<T> class:

 public class ResponseScope<T> : IDisposable where T : class {    public readonly T Response;    public ResponseScope( );    public ResponseScope(string bindingConfiguration);    public ResponseScope(NetMsmqBinding binding);    public void Dispose( ); } 

ResponseScope<T> is a disposable objectit installs a new operation context, and when it is disposed of, the scope restores the old operation context. To automate that even in the face of exceptions, ResponseScope<T> should be used in a using statement. ResponseScope<T> takes a type parameter representing the response contract, and it offers the Response public, read-only field of the same type. Response is a proxy to the response service, and the clients of ResponseScope<T> use Response to call operations on the response service. There is no need to dispose of Response because ResponseScope<T> does that in Dispose( ). ResponseScope<T> will instantiate Response based on the default endpoint in the config file or using the binding information (the config section name or an actual binding instance) provided to the constructor. Using ResponseScope<T>, the finally statement of Example 9-24 is reduced to:

 finally {    using(ResponseScope<ICalculatorResponse> scope                                         = new ResponseScope<ICalculatorResponse>( ))    {       scope.Response.OnAddCompleted(result,error);    } } 

Example 9-29 shows the implementation of ResponseScope<T>.

Example 9-29. Implementing ResponseScope<T>

 public class ResponseScope<T> : IDisposable where T : class {    OperationContextScope m_Scope;    public readonly T Response;    public ResponseScope() : this(new NetMsmqBinding( ))    {}    public ResponseScope(string bindingConfiguration) :                                      this(new NetMsmqBinding(bindingConfiguration))    {}    public ResponseScope(NetMsmqBinding binding)    {       ResponseContext responseContext = ResponseContext.Current;       EndpointAddress address                             = new EndpointAddress(responseContext.ResponseAddress);       ChannelFactory<T> factory = new ChannelFactory<T>(binding,address);       QueuedServiceHelper.VerifyQueue(factory.Endpoint);       Response = factory.CreateChannel( );       //Switching context now       m_Scope = new OperationContextScope(Response as IContextChannel);       ResponseContext.Current = responseContext;    }    public void Dispose( )    {       IDisposable disposable = Response as IDisposable;       disposable.Dispose( );       m_Scope.Dispose( );    } } 

The constructor of ResponseScope<T> uses ResponseContext.Current to extract the incoming response context. It then uses a channel factory to both verify that the response queue exists and to initialize Response with a proxy to the response service. The trick in implementing ResponseScope<T> is using OperationContextScope not inside a using statement. By constructing a new OperationContextScope, ResponseScope<T> establishes a new operation context. ResponseScope<T> encapsulates an OperationContextScope and it saves the newly created OperationContextScope. The old context will be restored once ResponseScope<T> is disposed of in a using statement. ResponseScope<T> then uses ResponseContext.Current to add to the new operation context's outgoing headers the response context and returns.

9.9.5.3. Streamlining the response service-side

The response service needs to extract the method ID from the incoming message headers. To automate that, simply use ResponseContext.Current. Once you have done so, the OnAddCompleted( ) method of Example 9-25 is reduced to:

 public void OnAddCompleted(int result,ExceptionDetail error) {    string methodId = ResponseContext.Current.MethodId;    if(error == null)    {       AddCompleted(methodId,result);    }    else    {       AddError(methodId);    } } 

9.9.6. Transactions

A queued service typically queues up the response as part of the incoming playback transaction. Given the queued service definition of Example 9-30, Figure 9-13 depicts the resulting transaction and the participating actions.

Example 9-30. Queuing up a response as part of the playback transaction

 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyCalculator : ICalculator {    [OperationBehavior(TransactionScopeRequired = true)]    public void Add(int number1,int number2)    {       ...       try       {          ...       }       catch//Do not re-throw       {          ...       }       finally       {          using(ResponseScope<ICalculatorResponse> scope                                         = new ResponseScope<ICalculatorResponse>( ))          {             scope.Response.OnAddCompleted(...);          }       }    } } 

Figure 9-13. Queuing up in the playback transaction


The nice thing design-wise about having the queued call playback and the queued response in the same transaction is that if the playback transaction is aborted for whatever reason (including due to other services in the transaction) the response is canceled automatically. This is by far the common choice for most applications. Note in Example 9-30 that the service catches all exceptions and does not rethrow them. This is important, because any unhandled exception (or rethrown exception) will abort the response, and the service therefore has no point in ever bothering to respond. Using a response service intrinsically means the service does not rely on the automatic retry mechanism of WCF, and it handles its own business logic failures because the clients expect it to respond in a prescribed manner.

9.9.6.1. Using a new transaction

Alternatively to always having the response be part of the playback transaction, the service can also respond in a new transaction by encasing the response in a new transaction scope, as shown in Example 9-31 and graphically in Figure 9-14.

Example 9-31. Responding in a new transaction

 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyCalculator : ICalculator {    [OperationBehavior(TransactionScopeRequired = true)]    public void Add(int number1,int number2)    {       ...       finally       {          using(TransactionScope transactionScope =                           new TransactionScope(TransactionScopeOption.RequiresNew))          using(ResponseScope<ICalculatorResponse> responseScope                                         = new ResponseScope<ICalculatorResponse>( ))          {             scope.Response.OnAddCompleted(...);          }       }    } } 

Figure 9-14. Responding in a new transaction


Responding in a new transaction is required in two cases. The first is when the service wants to respond regardless of the outcome of the playback transaction, which could be aborted by other downstream services. The second case is when the response is nice to have, and the service does not mind if the playback transaction commits but the response aborts.

9.9.6.2. Response service and transactions

Since the response service is just another queued service, the mechanics of managing and participating in a transaction are just like those of any other queued service. However, there are a few points worth mentioning in this particular context. The response service can process the response as part of the incoming response playback transaction:

 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyCalculatorResponse : ICalculatorResponse {    [OperationBehavior(TransactionScopeRequired = true)]    public void OnAddCompleted(...)    {...} } 

This is by far the most common option because it allows for retries. That said, the response service should avoid lengthy processing of the queued response because it may risk aborting the playback transaction. The response service can process the response in a separate transaction if the response is nice to have (as far as the provider of the response service in concerned):

 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyCalculatorResponse : ICalculatorResponse {    public void OnAddCompleted(int result,ExceptionDetail error)    {       using(TransactionScope scope =                           new TransactionScope(TransactionScopeOption.RequiresNew))       {...}    } } 

By processing the response in a new transaction, if that transaction aborts, WCF will not retry the response out of the response service queue. Finally, for response processing of long duration, you could configure the response service not to use a transaction at all (including the playback transaction):

 [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class MyCalculatorResponse : ICalculatorResponse {    public void OnAddCompleted(...)    {...} } 




Programming WCF Services
Programming WCF Services
ISBN: 0596526997
EAN: 2147483647
Year: 2004
Pages: 148
Authors: Juval Lowy

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net