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.
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.
When a client without an ambient transaction calls a sessiongram-less queued endpoint (as in Example 9-11), 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 9-11. 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();
}
With a transactional client (that is, client code with an ambient transaction) of a sessiongram-less queued endpoint (as in Example 9-12), 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 9-12. 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()
.
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.
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.
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
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 Transaction
Auto
Complete
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.
Only a sessionful service can support a sessiongram
contract, since only a service configured with InstanceContextMode.PerSession
can set
Transaction
Auto
Complete
to false
.
To further enforce this
constraint, the service cannot rely on setting Release
Service
Instance
On
Transaction
Complete
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 9-13 is a template for implementing a queued sessionful service.
Example 9-13. 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() {...} }
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. While you could apply the
techniques described in Chapter 7 to create a
state-aware transactional singleton, you can also restore the
singleton semantic by setting the ReleaseServiceInstanceOnTransactionComplete
property to false
and use volatile
resource managers.
Example 9-14 shows a template for implementing a transactional queued singleton.
Example 9-14. 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 }
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.
18.223.33.157