Demarcating Operations

Sometimes, a sessionful contract has an implied order of operation invocations. Some operations cannot be called first, while other operations must be called last. For example, consider this contract, used to manage customer orders:

[ServiceContract(SessionMode = SessionMode.Required)]
interface IOrderManager
{
   [OperationContract]
   void SetCustomerId(int customerId);

   [OperationContract]
   void AddItem(int itemId);

   [OperationContract]
   decimal GetTotal(  );

   [OperationContract]
   bool ProcessOrders(  );
}

The contract has the following constraints: the client must provide the customer ID as the first operation in the session, or else no other operations can take place; items may be added, and the total calculated, and as often as the client wishes; processing the order terminates the session, and therefore must come last. In classic .NET, such requirements often forced the developers to support some state machine or state flags and to verify the state on every operation.

WCF, however, allows contract designers to designate contract operations as operations that can or cannot start or terminate the session, using the IsInitiating and IsTerminating properties of the OperationContract attribute:

[AttributeUsage(AttributeTargets.Method)]
public sealed class OperationContractAttribute : Attribute
{
   public bool IsInitiating
   {get;set;}
   public bool IsTerminating
   {get;set;}
   //More members
}

These properties can be used to demarcate the boundary of the session; hence, I call this technique demarcating operations. At service load time (or during the proxy use time on the client side), if these properties are set to their nondefault values, WCF verifies that the demarcating operations are part of a contract that mandates sessions (i.e., that SessionMode is set to SessionMode.Required) and throws an InvalidOperationException otherwise. Both a sessionful service and a singleton can implement contracts that use demarcating operations to manage their client sessions.

The default values of these properties are true for IsInitiating and false for IsTerminating. Consequently, these two definitions are equivalent:

[OperationContract]
void MyMethod(  );

[OperationContract(IsInitiating = true,IsTerminating = false)]
void MyMethod(  );

As you can see, you can set both properties on the same method. In addition, operations do not demarcate the session boundary by default—operations can be called first, last, or in between any other operations in the session. Using nondefault values enables you to dictate that a method is not called first, or that it is called last, or both:

[ServiceContract(SessionMode = SessionMode.Required)]
interface IMyContract
{
   [OperationContract]
   void StartSession(  );

   [OperationContract(IsInitiating = false)]
   void CannotStart(  );

   [OperationContract(IsTerminating = true)]
   void EndSession(  );

   [OperationContract(IsInitiating = false,IsTerminating = true)]
   void CannotStartCanEndSession(  );
}

Going back to the order-management contract, you can use demarcating operations to enforce the interaction constraints:

[ServiceContract(SessionMode = SessionMode.Required)]
interface IOrderManager
{
   [OperationContract]
   void SetCustomerId(int customerId);

   [OperationContract(IsInitiating = false)]
   void AddItem(int itemId);

   [OperationContract(IsInitiating = false)]
   decimal GetTotal(  );

   [OperationContract(IsInitiating = false,IsTerminating = true)]
   bool ProcessOrders(  );
}
//Client code
OrderManagerClient proxy = new OrderManagerClient(  );

proxy.SetCustomerId(123);
proxy.AddItem(4);
proxy.AddItem(5);
proxy.AddItem(6);
proxy.ProcessOrders(  );

proxy.Close(  );

When IsInitiating is set to true (its default), it means the operation will start a new session if it is the first method the client calls but will be part of the ongoing session if another operation is called first. When IsInitiating is set to false, it means that a client can never call that operation as the first operation in a new session, and that the method can only be part of an ongoing session.

When IsTerminating is set to false (its default), it means the session continues after the operation returns. When IsTerminating is set to true, it means the session terminates once the method returns, and WCF disposes of the service instance asynchronously. The client will not be able to issue additional calls on the proxy. Note that the client should still close the proxy.

Tip

When you generate a proxy to a service that uses demarcating operations, the imported contract definition contains the property settings. In addition, WCF enforces the demarcation separately on the client and service sides, so you could actually employ them independently.

Get Programming WCF Services, 2nd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.