Maintaining State and Sequencing Operations in Windows Communication Foundation 4
- 11/23/2010
- Managing State in a WCF Service
- Sequencing Operations in a WCF Service
- Maintaining State by Using a Durable Service
- Summary
Sequencing Operations in a WCF Service
When using the PerSession instance context mode, it is often useful to be able to control the order in which a client application invokes operations in a WCF service. Revisiting the ShoppingCartService service, suppose that you decide to use the PerSession instance context mode rather than PerCall. In this scenario, it might not make sense to allow the client application to remove an item from the shopping cart, query the contents of the shopping cart, or perform a checkout operation if the user has not actually added any items to the shopping cart. Equally, it would be questionable practice to allow the user to add an item to the shopping cart after the user has already checked out and paid for the items in the cart. There is actually a sequence to the operations in the ShoppingCartService service, and the service should enforce this sequence:
Add an item to the shopping cart.
Add another item, remove an item, or query the contents of the shopping cart.
Check out and empty the shopping cart.
When you define an operation in a service contract, the OperationContract attribute provides two Boolean properties that you can use to control the order of operations and the consequent lifetime of the service instance:
IsInitiating If you set this property to true, a client operation can invoke this operation to initiate a new session and create a new service instance. If a session already exists, this property has no further effect. By default, this property is set to true. If you set this property to false, then a client application cannot invoke this operation until another operation has initiated the session and created a service instance. At least one operation in a service contract must have this property set to true.
IsTerminating If you set this property to true, the WCF runtime will terminate the session and release the service instance when the operation completes. The client application must create a new connection to the service before invoking another operation, which must have the IsInitiating property set to true. The default value for this property is false. If no operations in a service contract specify a value of true for this property, the session remains active until the client application closes the connection to the service.
The WCF runtime checks the values of these properties for consistency at runtime in conjunction with another property for the service contract called SessionMode. The SessionMode property of the service contract specifies whether the service implements sessions. It can have one of the following values:
SessionMode.Required The service will create a session to handle client requests if a session does not already exist for this client; otherwise, it will use the existing session for the client. The binding used by the service must support sessions. For example, the ws2007HttpBinding binding supports sessions, but the basicHttpBinding binding does not.
SessionMode.Allowed The service will create or use a session if the service binding supports them; otherwise, the service will not implement sessions.
SessionMode.NotAllowed The service will not use sessions, even if the service binding supports them.
If you specify a value of false for the IsInitiating property of any operation, then you must set the SessionMode property of the service contract to SessionMode.Required. If you do not, the WCF runtime will throw an exception. Similarly, you can only set the IsTerminating property to true if the SessionMode property of the service is set to SessionMode.Required.
In the next set of exercises, you will see how to apply the IsInitiating and IsTerminating properties of the OperationBehavior attribute.
Control the Sequence of Operations in the ShoppingCartService Service
In Visual Studio, open the IShoppingCartService.cs file for the ShoppingCartService project in the Code And Text Editor window.
Add the SessionMode property to the ServiceContract attribute for the IShoppingCartService interface, as shown in bold in the following:
[ServiceContract(SessionMode = SessionMode.Required, Namespace = "http://adventure-works.com/2007/03/01", Name = "ShoppingCartService")] public interface IShoppingCartService { ... }
Remember that this setting enforces the requirement for the service to create a session to handle requests from client applications. The service currently implements the PerCall instance context mode, and you will change this setting shortly.
Modify the operations in the IShoppingCartService interface by specifying which operations initiate a session and which operations cause a session to terminate, as follows:
public interface IShoppingCartService { [OperationContract(Name="AddItemToCart", IsInitiating = true)] bool AddItemToCart(string productNumber); [OperationContract(Name = "RemoveItemFromCart", IsInitiating = false)] bool RemoveItemFromCart(string productNumber); [OperationContract(Name = "GetShoppingCart", IsInitiating = false)] string GetShoppingCart(); [OperationContract(Name = "Checkout", IsInitiating = false, IsTerminating = true)] bool Checkout(); }
Open the ShoppingCartService.cs file for the ShoppingCartService project in the Code And Text Editor window. Change the InstanceContextMode property of the service to create a new instance of the service for each session, as shown in bold in the following:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] public class ShoppingCartServiceImpl : IShoppingCartService { ... }
In the AddItemToCart method, comment out the single statement that calls the restoreShoppingCart method and the two statements that call the saveShoppingCart method.
The service is using the PerSession instance context mode. Therefore, the session will maintain its own copy of the user’s shopping cart in memory, which renders these method calls unnecessary.
In the RemoveItemFromCart method, comment out the statement that calls the restoreShoppingCart method and the statement that calls the saveShoppingCart method.
In the GetShoppingCart method, comment out the statement that calls the restoreShoppingCart method.
You can test the effects of these changes by modifying the client application.
Test the Operation Sequencing in the ShoppingCartService Service
Open the ShoppingCartServiceProxy.cs file for the ShoppingCartClient project. This is the proxy class that you generated earlier. You have modified the service contract, so you must update this class to reflect these changes. You can use the svcutil utility to generate a new version of the proxy, but the changes are quite small so it is easier to add them by hand, as follows:
Modify the ServiceContract attribute for the ShoppingCartService interface and specify the SessionMode property:
[System.ServiceModel.ServiceContractAttribute( SessionMode=System.ServiceModel.SessionMode.Required, Namespace="...", ...)] public interface ShoppingCartService { ... }
Add the IsInitiating property to the OperationContract attribute of the AddItemToCart, RemoveItemFromCart, and GetShoppingCart methods:
[System.ServiceModel.OperationContractAttribute(IsInitiating = true, Action="...", ...)] bool AddItemToCart(string productNumber); [System.ServiceModel.OperationContractAttribute(IsInitiating = false, Action="...", ...)] bool RemoveItemFromCart(string productNumber); [System.ServiceModel.OperationContractAttribute(IsInitiating = false, Action="...", ...)] string GetShoppingCart();
Add the IsInitiating property and the IsTerminating property to the OperationContract attribute of the Checkout method:
[System.ServiceModel.OperationContractAttribute( IsInitiating = false, IsTerminating = true, Action="...", ...)] bool Checkout();
Edit the Program.cs file in the ShoppingCartClient project. Add the following statements (shown in bold) between the code that displays the shopping cart and the statement that closes the proxy:
static void Main(string[] args) { ... try { ... // Query the shopping cart and display the result string cartContents = proxy.GetShoppingCart(); Console.WriteLine(cartContents); // Buy the goods in the shopping cart proxy.Checkout(); Console.WriteLine("Goods purchased"); // Go on another shopping expedition and buy more goods // Add a road seat assembly to the shopping cart proxy.AddItemToCart("SA-R127"); // Add a touring seat assembly to the shopping cart proxy.AddItemToCart("SA-T872"); // Remove the road seat assembly proxy.RemoveItemFromCart("SA-R127"); // Display the shopping basket cartContents = proxy.GetShoppingCart(); Console.WriteLine(cartContents); // Buy these goods as well proxy.Checkout(); Console.WriteLine("Goods purchased"); // Disconnect from the ShoppingCartService service proxy.Close(); } ... }
The first statement that invokes the Checkout operation terminates the session and destroys the shopping cart. The statements that follow create and use a new session, with its own shopping cart.
Start the solution without debugging. In the ShoppingCartClient console window, press Enter.
The client application adds the three items to the shopping cart and outputs the contents. It then displays an error:
This demonstrates that the first call to the Checkout operation successfully terminated the session. However, the service has closed the connection that the client application was using when the session finished. Therefore, the client application must open a new connection and create a new session before it can communicate with the service again.
Press Enter to close the client application console window. In the host application console window, press Enter to stop the service.
The simplest way to create a new connection is to rebuild the proxy. However, you must ensure that you provide the user’s credentials again because these will be lost when the new instance of the proxy is created.
In Visual Studio, add the following statements after the code that performs the first Checkout operation in the Main method:
static void Main(string[] args) { ... try { ... // Buy the goods in the shopping cart proxy.Checkout(); Console.WriteLine("Goods purchased"); // Go on another shopping expedition and buy more goods proxy = new ShoppingCartServiceClient( "WS2007HttpBinding_IShoppingCartService"); // Provide credentials to identify the user proxy.ClientCredentials.Windows.ClientCredential.Domain = "Domain"; proxy.ClientCredentials.Windows.ClientCredential.UserName = "Fred"; proxy.ClientCredentials.Windows.ClientCredential.Password = "Pa$$w0rd"; // Add a road seat assembly to the shopping cart proxy.AddItemToCart("SA-R127"); ... } ... }
Start the solution again. In the ShoppingCartClient console window, press Enter.
This time, the client application creates a second session after terminating the first. The second session has its own shopping cart:
Press Enter to close the client application console window. In the host application console window, press Enter to stop the service.
As an additional exercise, you can test the effects of invoking the RemoveItemFromCart, GetShoppingCart, or Checkout operations without calling AddItemToCart first. These operations do not create a new session, and the client application should fail, with the exception shown in Figure 7-1.
Figure 7-1 Invoking operations in the wrong sequence.