The 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 nature. However, the queued service may need to report back to its client on the result of the invocation, or return results or even errors. By default, 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 a client posts a queued call to a disconnected service, by the time the service finally gets the message and processes it, there may no longer be a client to return the values to. The solution is to have the service report back to a client-provided queued service. I call such a service a response service.[7] Figure 9-12 shows the architecture of such a solution.

A response service

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, or it may share the client's process, or be hosted in a separate process or even on a separate machine. If the response service shares the client's process, when the client is launched the response service will start processing the queued responses. Having the response service in a separate process (or even on a separate machine) from the client's helps to further decouple lifeline-wise the response service from the client or clients that use it.

Tip

Not all queued services require a response service. Be pragmatic, and use a response service only where appropriate; that is, where it adds the most value.

Designing a Response Service Contract

As with any WCF service, the client and the service need to agree beforehand on the response contract and what it will be used for; that is, whether it will be used 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. As an 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
}
class MyCalculator : ICalculator
{...}

The MyCalculator service is required to respond 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. The ICalculatorResponse contract could be defined as:

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

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

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

   //More operations
}

As demonstrated by Example 9-21, the response service is just that—a simple service. There is nothing special about it other than its designation as a response service.

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 enough 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. Note that the service typically has no direct use for the ID; all it needs to do is pass it along.

The second problem is how to enable the queued service to discover the address of the response service. Unlike with duplex callbacks, there is no built-in support in WCF for passing the response service's reference to the queued service, so the queued service needs to manually 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, the queued service lacks the address of the response service to be able to respond. You could place that address in the service host config file (in a client section) but 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);
}

The ResponseContext class

While passing the address and the ID as explicit parameters would work, it does distort the original contract, and it introduces plumbing-level parameters alongside 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). Appendix B explains in detail the use of the incoming and outgoing headers, including the related techniques and supporting classes in ServiceModelEx.

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

Example 9-22. The ResponseContext class

[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(string responseAddress) : this(responseAddress,
                                                         Guid.NewGuid(  ).
ToString(  ))
   {}
   public ResponseContext(string responseAddress,string methodId,
                          string faultAddress)
   {
      ResponseAddress = responseAddress;
      MethodId = methodId;
      FaultAddress = faultAddress;
   }

   public static ResponseContext Current
   {
      get
      {
         return GenericContext<ResponseContext>.Current.Value;
      }
      set
      {
         GenericContext<ResponseContext>.Current =
                                        new GenericContext<ResponseContext>(value);
      }
   }
   //More members
}

ResponseContext provides a place to store both the response address and the ID. In addition, if the client wants to use a separate response service for faults, ResponseContext 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 can also use the constructor of ResponseContext, which takes just the response address, and have that constructor generate a GUID for the ID. To streamline the act of storing a ResponseContext instance in and retrieving it from the headers, ResponseContext provides the Current property, which merely encapsulates my GenericContext<T>. 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.

Client-Side Programming

My HeaderClientBase<T,H> proxy base class (defined in Appendix B) is designed to automate passing information in the headers from the client to the service:

public abstract partial class HeaderClientBase<T,H> : ClientBase<T> where T : class
{
   protected H Header
   {get;set;}

   public HeaderClientBase(H header);
   public HeaderClientBase(H header,string endpointName);

   //More members
}

However, when it comes to the response context, there are differences compared with the raw headers management discussed in Appendix B: specifically, changing the context (that is, the headers) on each call as opposed to setting it only at construction time, generating method IDs and providing them to the client, and enqueuing rather than merely invoking the service call. While the client can easily use my HeaderClientBase<T,H> to do all that, all clients will have to repeat such code for every contract and proxy they have. It is better to automate and encapsulate these steps in a dedicated proxy base class such as my ResponseClientBase<T>, shown in Example 9-23.

Example 9-23. The ResponseClientBase<T> class

public abstract class ResponseClientBase<T> :
                               HeaderClientBase<T,ResponseContext> where T : class
{
   public readonly string ResponseAddress;

   public ResponseClientBase(string responseAddress)
   {
      ResponseAddress = responseAddress;
      Endpoint.VerifyQueue(  );
   }
   public ResponseClientBase(string responseAddress,string endpointName)
   {...}
   public ResponseClientBase(string responseAddress,
                             NetMsmqBinding binding,EndpointAddress address)
   {...}

   /* More constructors */

   protected string Enqueue(Action action)
   {
      string methodId = GenerateMethodId(  );
      Header = new ResponseContext(ResponseAddress,methodId);
      action(  );
      return Header.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 the endpoint name, address, and binding. The constructors store the response address in the read-only public field ResponseAddress. In addition, the constructors use the VerifyQueue( ) endpoint extension method to verify that the service queue (and the DLQ) exists and to create it if necessary.

ResponseClientBase<T> provides the virtual GenerateMethodId( ) method, which by default uses a GUID for the method ID. However, your subclasses of ResponseClientBase<T> can override it and provide their own unique strings, such as an incremented integer.

The heart of ResponseClientBase<T> is the Enqueue( ) method. Enqueue( ) accepts a delegate wrapping the operation to invoke. For every operation invoked by the client, Enqueue( ) generates a new method ID, provides it to a new ResponseContext object (along with the response address supplied to the constructor), and assigns the new ResponseContext object to the Header property of HeaderClientBase<T,H>. Thanks to generics, Header is of the type ResponseContext. After invoking the delegate, Enqueue( ) returns the method ID used for that call.

Using ResponseClientBase<T>

When using a ResponseClientBase<T>-derived proxy, I wanted to have all operations of the proxy return the method ID used to dispatch them (something that is very handy when managing the responses on the client side).

To that end, unlike with a normal proxy, when deriving from ResponseClientBase<T>, do not have the subclass also derive from the contract. Instead, you must 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—the operations on the contract are all one-way and do not return anything).

You need merely provide the contract type as a type parameter to ResponseClientBase<T> and have your proxy's implementation of the operations return the method ID returned to them by Enqueue( ). For example, for this calculator contract:

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

Example 9-24 shows the matching service proxy.

Example 9-24. 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 address) :
                                             base(responseAddress,binding,address)
   {}
   //More constructors

   public string Add(int number1,int number2)
   {
      return Enqueue((  )=>Channel.Add(number1,number2));
   }

   //More operations
}

Using the proxy in Example 9-24 yields this straightforward client code:

string responseAddress = "net.msmq://localhost/private/MyCalculatorResponseQueue";

CalculatorClient proxy = new CalculatorClient(responseAddress);
string methodId = proxy.Add(2,3);
proxy.Close(  );

Note how closely the client that provides the response address to the proxy corresponds to a client that provides a duplex callback object to a proxy (as demonstrated in Chapter 5). In the queued services world, the response service address is the equivalent callback reference.

Tip

A queued response service is not limited to being used 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. You will need to rework ResponseClientBase<T> so that it only uses Binding, and rename Enqueue( ) to InvokeService( ).

Queued Service-Side Programming

Since duplex callback was my reference programming model in designing the support for a response service, I wanted to provide an easy way for the service to obtain a proxy for the response service (on par with the duplex case, which simply obtains the callback reference from the operation context). My solution was ResponseScope<T>, defined as:

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> automates reading the response context from the message headers, writing the response itself to the outgoing headers, and creating (and closing) a proxy to the response service queue. All you need to do is instantiate a ResponseScope<T> object and optionally provide it with the binding or binding configuration. The Response read-only field will contain a reference to a proxy to the response service.

Example 9-25 demonstrates using ResponseScope<T>.

Example 9-25. Using ResponseScope<T>

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 rethrow
      catch(Exception exception)
      {
         error = new ExceptionDetail(exception);
      }
      finally
      {
         using(ResponseScope<ICalculatorResponse> scope =
                                         new ResponseScope<ICalculatorResponse>(  ))
         {
            scope.Response.OnAddCompleted(result,error);
         }
      }
   }
}

In Example 9-25, the MyCalculator service catches any exception 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, in the context of transactions and response services, rethrowing the exception 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 WCF's playback error handling.

In the finally statement, regardless of exceptions, the service responds. It creates a new ResponseScope<T> object and uses the Response field to call the response service proxy, in effect enqueuing the response. The service then disposes of the response scope.

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

Example 9-26. 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);
      factory.Endpoint.VerifyQueue(  );

      Response = factory.CreateChannel(  );

      //Switching context now
      m_Scope = new OperationContextScope(Response as IContextChannel);

      ResponseContext.Current = responseContext;
   }
   public void Dispose(  )
   {
      using(Response as IDisposable)
      using(m_Scope)
      {}
   }
}

This terse example contains some of the most advanced WCF code in this book. ResponseScope<T> is a disposable object—it installs a new operation context (as explained in Appendix B), and when ResponseScope<T> is disposed of, the scope restores the old operation context. The trick in implementing ResponseScope<T> is not to use an OperationContextScope instance in a using statement to scope a piece of code with a new operation context, but rather to create it in one place, store it in the m_Scope member, and dispose of m_Scope in another place. The constructor of OperationContextScope is what swaps the contexts, so merely constructing the scope swaps contexts. The Dispose( ) method of ResponseScope<T> restores the original operation context by disposing of m_Scope. To automate that behavior even in the face of exceptions, ResponseScope<T> should be used in a using statement. ResponseScope<T> takes a generic type parameter representing the response contract, and Response is of that 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( ).

The constructor of ResponseScope<T> first uses ResponseContext.Current to extract the incoming response context. Since ResponseScope<T> reads the response address from the message headers of the service operation context at runtime, it must programmatically construct a proxy to the response service, using a channel factory. Note that the service can still read the exact settings to use for the NetMsmqBinding from the config file, by specifying the binding configuration name to the binding constructor. After instantiating Response, ResponseScope<T> swaps operation contexts; it then uses ResponseContext.Current to add to the new operation context's outgoing headers the response context and returns.

Note that ResponseScope<T> sends the response service the entire response context (not just the ID). This is done both for simplicity's sake and because it may be beneficial for the response service to have access to the fault and response address used.

Response Service-Side Programming

The response service accesses its response context, reads from it the method ID, and responds accordingly. Example 9-27 demonstrates a possible implementation of such a response service.

Example 9-27. Implementing a response service

class MyCalculatorResponse : ICalculatorResponse
{
   [OperationBehavior(TransactionScopeRequired = true)]
   public void OnAddCompleted(int result,ExceptionDetail error)
   {
      string methodId = ResponseContext.Current.MethodId;
      ...
   }
}

Tip

It is common for the response service to update the application's user interfaces with the queued results (or errors). Chapter 8 introduced my FormHost<F> class, which you can certainly leverage to support the queued response contract. For example:

class CalculatorResponse :
                        FormHost<CalculatorResponse>,
                                  ICalculatorResponse
{
   [OperationBehavior(TransactionScopeRequired=true)]
   public void OnAddCompleted(int result,
                              ExceptionDetail error)
   {
      Text = "Add returned: " + result;
      ...
   }
}

In fact, nothing prevents you from having the client itself be the response service as well.

Transactions

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

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

class MyCalculator : ICalculator
{
   [OperationBehavior(TransactionScopeRequired = true)]
   public void Add(int number1,int number2)
   {
      ...
      try
      {
         ...
      }
      catch //Do not rethrow
      {
         ...
      }
      finally
      {
         using(ResponseScope<ICalculatorResponse> scope =
                                         new ResponseScope<ICalculatorResponse>(  ))
         {
            scope.Response.OnAddCompleted(...);
         }
      }
   }
}
Queuing up in the playback transaction

Figure 9-13. Queuing up in the playback transaction

Design-wise, the nice thing 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 aborting), the response is canceled automatically. This is by far the most common choice for most applications.

Note in Example 9-28 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, so there won't be any point in the service bothering to respond. Using a response service intrinsically means that 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.

Using a new transaction

As an alternative to always having the response be part of the playback transaction, the service can respond in a new transaction by encasing the response in a new transaction scope, as shown in Example 9-29 and illustrated in Figure 9-14.

Example 9-29. Responding in a new transaction

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>(  ))
  {
            responseScope.Response.OnAddCompleted(...);
         }
      }
   }
}
Responding in a new transaction

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.

Response service and transactions

Since a 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:

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 is concerned):

class MyCalculatorResponse : ICalculatorResponse
{
   public void OnAddCompleted(int result,ExceptionDetail error)
   {
      using(TransactionScope scope = new TransactionScope(  ))
      {...}
   }
}

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

class MyCalculatorResponse : ICalculatorResponse
{
   public void OnAddCompleted(...)
   {...}
}


[7] I first published my initial technique for a response service in the February 2007 issue of MSDN Magazine. In the next version of WCF and the .NET Framework, Microsoft intends to incorporate a similar solution to the one presented here.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.97.202