ENTERPRISE

Programming WCF Services : Queued Services - Instance Management

4/28/2011 4:22:27 PM
The contract session mode and the service instance mode have a paramount effect on the behavior of the queued calls, the way the calls are played back to the service, and the overall program workflow and allowed assumptions. The MSMQ binding cannot maintain a transport session in the connected sense, since the client is inherently disconnected. Instead, the equivalent MSMQ concept is called a sessiongram. If the contract is configured with SessionMode.Allowed (the default) or SessionMode.NotAllowed, there will be no sessiongram. Every call the client makes on the proxy will be converted to a single WCF message, and those WCF messages will be placed in individual MSMQ messages and posted to the queue. A client making two calls on the proxy will result in two MSMQ messages. If the contract is configured with SessionMode.Required, all the calls made by the client against the same proxy will be packaged in a single MSMQ message, in the order in which they were made and posted to the queue. On the service side, WCF will play the calls from the MSMQ message in the order they were made (like a recording) to the same service instance. This mode is therefore analogous to a transport session and a sessionful service.

1. Per-Call Queued Services

In the case of a per-call service, the client has no way of knowing whether its calls will eventually end up being played to a queued per-call service. All the client sees is the session mode of the contract. If the session mode is either SessionMode.Allowed or SessionMode.NotAllowed, there will be no sessiongram. In this case, regardless of whether the service is configured as per-call or sessionful it will amount to the same result: per-call processing and instantiation.

1.1. Nontransactional clients

When a client without an ambient transaction calls a sessiongram-less queued endpoint (as in Example 1), the MSMQ messages generated for each call are posted to the queue immediately after each call. If the client has an exception, the messages posted up to that point are not rejected and are delivered to the service.

Example 1. Nontransactional client of a sessionless queued endpoint
[ServiceContract]
interface IMyContract
{
[OperationContract(IsOneWay = true)]
void MyMethod();
}
//Client code
using(TransactionScope scope =
new TransactionScope(TransactionScopeOption.Suppress))
{
MyContractClient proxy = new MyContractClient();

proxy.MyMethod(); //Message posts to queue here
proxy.MyMethod(); //Message posts to queue here

proxy.Close();
}


1.2. Transactional clients

With a transactional client (that is, client code with an ambient transaction) of a sessiongram-less queued endpoint (as in Example 2), the messages corresponding to each call are posted to the queue only when the client’s transaction commits. If the client transaction aborts, all of those messages are rejected from the queue and all calls are canceled.

Example 2. Transactional client of a sessionless queued endpoint
[ServiceContract]
interface IMyContract
{
[OperationContract(IsOneWay = true)]
void MyMethod();
}
//Client code
using(TransactionScope scope = new TransactionScope())
{
MyContractClient proxy = new MyContractClient();

proxy.MyMethod(); //Message written to queue
proxy.MyMethod(); //Message written to queue

proxy.Close();
scope.Complete();
} //Messages committed to queue here

There is no relationship between the proxy and the ambient transaction. If the client uses a transaction scope (as in Example 9-12), the client can close the proxy inside or outside the scope and may continue to use the proxy even after the transaction ends, or in a new transaction. The client may also close the proxy before or after the call to Complete().

1.3. Per-call processing

On the host side, the queued calls are dispatched separately to the service, and each call is played to a separate service instance. This is the case even if the service instance mode is per-session. I therefore recommend that when using a sessiongram-less queued contract, you should always explicitly configure the service as per-call and configure the contract for disallowing sessions, to increase the readability of the code and clearly convey your design decision:

[ServiceContract(SessionMode = SessionMode.NotAllowed)]
interface IMyContract
{...}

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class MyService : IMyContract
{...}

After each call the service instance is disposed of, just as with a connected per-call service. The per-call service may or may not be transactional. If it is transactional and the playback transaction is aborted, only that particular call is rolled back to the queue for a retry. As you will see later, due to concurrent playback and WCF’s failure-handling behavior, calls to a per-call queued service can execute and complete in any order, and the client cannot make any assumptions about call ordering. Note that even calls dispatched by a transactional client may fail or succeed independently. Never assume order of calls with a sessiongram-less queued service.

2. Sessionful Queued Services

For sessionful queued services, the service contract must be configured with SessionMode.Required:

[ServiceContract(SessionMode = SessionMode.Required)]
interface IMyContract
{...}

class MyService : IMyContract
{...}

As mentioned previously, when the client queues up calls against a sessionful queued endpoint, all calls made throughout the session are grouped into a single MSMQ message. Once that single message is dispatched and played to the service, WCF creates a new dedicated service instance to handle all the calls in the message. All calls in the message are played back to that instance in their original order. After the last call, the instance is disposed of automatically.

WCF will provide both the client and the service with a unique session ID. However, the client session ID will be uncorrelated to that of the service. To approximate the session semantic, all calls on the same instance on the host side will share the same session ID.

2.1. Clients and transactions

In the case of a sessionful queued endpoint, the client must have an ambient transaction in order to call the proxy. Nontransactional clients are disallowed and will result in an InvalidOperationException:

[ServiceContract(SessionMode = SessionMode.Required)]
interface IMyContract
{
[OperationContract(IsOneWay = true)]
void MyMethod();
}

using(TransactionScope scope =
new TransactionScope(TransactionScopeOption.Suppress))
{
MyContractClient proxy = new MyContractClient();

proxy.MyMethod(); //Throws InvalidOperationException
proxy.MyMethod();

proxy.Close();
}


For a transactional client, WCF posts a single message to the queue when the transaction commits, and that single message is rejected from the queue if the transaction aborts:

using(TransactionScope scope = new TransactionScope())
{
MyContractClient proxy = new MyContractClient();

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

proxy.Close(); //Finish composing message, writes to queue

scope.Complete();
} //Single message committed to queue here

It is important to note that the single message prepared by the proxy must be posted to the queue within the same client transaction—that is, the client must end the session inside the transaction. If the client does not close the proxy before the transaction is complete, the transaction will always abort:

MyContractClient proxy = new MyContractClient();
using(TransactionScope scope = new TransactionScope())
{
proxy.MyMethod();
proxy.MyMethod();

scope.Complete();
} //Transaction aborts
proxy.Close();

This is required to enforce the atomicity of the sessiongram. All the calls in the session should either be posted to or rejected from the queue. If the client were to use the proxy in a second transaction that could commit or abort independently of the first, the results could be ambiguous or even dangerous.

An interesting side effect of this edict is that there is no point in storing a proxy to a queued sessionful endpoint in a member variable, because that proxy can only be used once in a single transaction and cannot be reused across client transactions.

Not only does the client have to close the proxy before the transaction ends, but when using a transaction scope, the client must close the proxy before completing the transaction. The reason is that closing the proxy to a queue’s sessionful endpoint requires accessing the current ambient transaction, which is not possible after calling Complete(). Trying to do so results in an InvalidOperationException:

MyContractClient proxy = new MyContractClient();
using(TransactionScope scope = new TransactionScope())
{
proxy.MyMethod();
proxy.MyMethod();

scope.Complete();
proxy.Close(); //Transaction aborts
}

A corollary of this requirement is that you cannot stack using statements in any order, because doing so may result in calling Dispose() in the wrong order (first on the scope, and then on the proxy):

using(MyContractClient proxy = new MyContractClient())
using(TransactionScope scope = new TransactionScope())
{
proxy.MyMethod();
proxy.MyMethod();

scope.Complete();

} //Transaction aborts

2.2. Services and transactions

A sessionful queued service must be configured to use transactions in all operations by setting TransactionScopeRequired to true. Failing to do so will abort all playback transactions. The service is required to have a transaction in every operation so that all the calls in the session fail or succeed as one atomic operation (i.e., so that a failure in one of the operations causes the entire queued session to fail). In addition, the transaction must be the same transaction for all operations in the session. Partial success is impossible here, because WCF cannot return only a portion of the MSMQ message back to the queue after a failure of one of the operations but not the others. The service must equate the session boundary with the transaction boundary. Do this by setting TransactionAutoComplete to false on all operations and relaying on TransactionAutoCompleteOnSessionClose to true. This will also have the added benefit of creating the affinity to the same transaction in all operations.


Note:

Only a sessionful service can support a sessiongram contract, since only a service configured with InstanceContextMode.PerSession can set TransactionAutoComplete to false.


To further enforce this constraint, the service cannot rely on setting ReleaseServiceInstanceOnTransactionComplete to false in order to restore the instance semantics while completing in each operation. Trying to do so will cause all queued sessions to always abort.

Example 3 is a template for implementing a queued sessionful service.

Example 3. Implementing a sessionful queued service
[ServiceContract(SessionMode = SessionMode.Required)]
interface IMyContract
{
[OperationContract(IsOneWay = true)]
void MyMethod1();

[OperationContract(IsOneWay = true)]
void MyMethod2();

[OperationContract(IsOneWay = true)]
void MyMethod3();
}

[ServiceBehavior(TransactionAutoCompleteOnSessionClose = true)]
class MyService : IMyContract
{
[OperationBehavior(TransactionScopeRequired = true,
TransactionAutoComplete = false)]
public void MyMethod1()
{...}

[OperationBehavior(TransactionScopeRequired = true,
TransactionAutoComplete = false)]
public void MyMethod2()
{...}


[OperationBehavior(TransactionScopeRequired = true,
TransactionAutoComplete = false)]
public void MyMethod3()
{...}
}


3. Singleton Service

A queued singleton service can never have a session and can only implement sessionless contracts. Configuring the SessionMode as either SessionMode.Allowed or SessionMode.NotAllowed has the same result: a sessiongram-less interaction. Consequently, I recommend always explicitly configuring the contracts of a queued singleton service as sessionless:

[ServiceContract(SessionMode = SessionMode.NotAllowed)]
interface IMyContract
{...}
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
class MyService : IMyContract
{...}

A nontransactional queued singleton service behaves like a regular WCF singleton as far as instancing. Regardless of the way the clients use their proxies, individual calls on the proxies are packaged into separate MSMQ messages and dispatched separately to the singleton, as with a per-call service. However, unlike with a per-call service, all these calls will be played back to the same single instance.

A transactional queued singleton, on the other hand, behaves by default like a per-call service, because after every call that completes the transaction WCF will release the singleton instance. The only difference between a true per-call service and a singleton is that WCF will allow at most a single instance of the singleton, regardless of the number of queued messages.

Example 4 shows a template for implementing a transactional queued singleton.

Example 4. Transactional queued singleton
[ServiceContract(SessionMode = SessionMode.NotAllowed)]
interface IMyContract
{
[OperationContract(IsOneWay = true)]
void MyMethod();
}

[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single,
ReleaseServiceInstanceOnTransactionComplete = false)]
class MySingleton : IMyContract,IDisposable
{
[OperationBehavior(TransactionScopeRequired = true)]
public void MyMethod()
{...}
//More members
}

3.1. Calls and order

Because the calls are packaged into individual MSMQ messages, they may be played to the singleton in any order (due to retries and transactions). In addition, calls may complete in any order, and even calls dispatched by a transactional client may fail or succeed independently. Never assume order of calls with a singleton.

Other  
  •  Programming WCF Services : Queued Services - Transactions
  •  Exchange Server 2010 : Implementing Client Access and Hub Transport Servers - Test Cmdlets for CAS and Hub Transport Servers
  •  Exchange Server 2010 : Implementing Client Access and Hub Transport Servers - Installing the Hub Transport Server
  •  Exchange Server 2010 : Implementing Client Access and Hub Transport Servers - Transport Pipeline
  •  Exchange Server 2010 : Implementing Client Access and Hub Transport Servers - Understanding the Hub Transport Server
  •  Implementing Client Access and Hub Transport Servers : Installing the Client Access Server
  •  Implementing Client Access and Hub Transport Servers : Understanding the Client Access Server (part 2)
  •  Implementing Client Access and Hub Transport Servers : Understanding the Client Access Server (part 1)
  •  SharePoint 2010 : Implementing and Managing In Place Records
  •  Understanding Exchange Policy Enforcement Security : Creating Messaging Records Management Policies
  •  Understanding Exchange Policy Enforcement Security : Implementing Transport Agent Policies on the Edge
  •  Safeguarding Confidential Data in SharePoint 2010 : Using Active Directory Rights Management Services (AD RMS) for SharePoint Document Libraries
  •  Safeguarding Confidential Data in SharePoint 2010 : Enabling TDE for SharePoint Content Databases
  •  Safeguarding Confidential Data in SharePoint 2010 : Using SQL Transparent Data Encryption (TDE)
  •  Safeguarding Confidential Data in SharePoint 2010 : Enabling SQL Database Mirroring
  •  Safeguarding Confidential Data in SharePoint 2010 : Outlining Database Mirroring Requirements
  •  Remote Administration of Exchange Server 2010 Servers : RDP with Exchange Server 2010 (part 2)
  •  Remote Administration of Exchange Server 2010 Servers : RDP with Exchange Server 2010 (part 1) - Planning and Using Remote Desktop for Administration
  •  Remote Administration of Exchange Server 2010 Servers : Using the ECP Remotely
  •  Safeguarding Confidential Data in SharePoint 2010 : Examining Supported Topologies
  •  
    Top 10
    Windows Server 2003 : Domain Name System - Command-Line Utilities
    Microsoft .NET : Design Principles and Patterns - From Principles to Patterns (part 2)
    Microsoft .NET : Design Principles and Patterns - From Principles to Patterns (part 1)
    Brother MFC-J4510DW - An Innovative All-In-One A3 Printer
    Computer Planet I7 Extreme Gaming PC
    All We Need To Know About Green Computing (Part 4)
    All We Need To Know About Green Computing (Part 3)
    All We Need To Know About Green Computing (Part 2)
    All We Need To Know About Green Computing (Part 1)
    Master Black-White Copying
    Most View
    Consumers Finally Dropping XP In Favour Of Windows 7
    jQuery 1.3 : Headline rotator
    The Language of Apple Platforms : Exploring the Objective-C File Structure
    Understanding the Architecture of SharePoint 2010 : Logical Architecture Components (part 1) - Service Architecture, Operating System Services
    Parallel Programming with Microsoft .Net : Parallel Aggregation - Design Notes
    System Builder - The Future Of USB
    10 Things You Need To Know About...Mobile Banking
    A Quick View Of The Industry: Big Data
    Mobile Viruses the risk keeps growing (Part 1)
    IIS 7.0 : Implementing Access Control - Authentication (part 1)
    Graphic Design – The Worship Of Icons
    Premiere Elements 11
    How Secure Is Your Pin? (Part 2)
    Motorola S1LK TRBO : Sleeker Voice
    The best browser hacks (part 2) - Google Chrome
    Toshiba MQ01ABD100 1TB Hard Drive
    Upgrading to Windows Server 2003 : Preparing Domains and Computers
    Bridal Masterclass
    Performing a typical Exchange Server 2010 install
    Business Software Releases & Update – December 2012