Managing State and Service Instances in a WCF service : Part 3 – Maintaining State with the PerCall Instance Context Mode

This post will show you how to maintain State with the PerCall Instance Context Mode. This is the third part of the series, discussing how to manage State and Service instances in WCF services. In the first two posts, we saw how to create a ShoppingCart WCF service where the service side was responsible for keeping the shopping cart state. In the second post, we discuss our options for manage Service Instances in WCF services and how the “PerCall” Instance Context mode, raises several issues that must be solved. If you remember, when changing the Instance Context Mode to “PerCall”, every time an operation call was completed, the shoppingCart list variable was destroyed, not letting us to use it for more calls. This means that we need to provide a mechanism to store and recreate the shopping cart each time the client application invokes an operation. So let’s do it.

I assume you have read the two previous posts, since I am gonna continue modifying the latest version of the project we created.

shoppingSolution

The plan is this: We are going to Serialize the shopping cart for each client, save it in a text file and retrieving it when asked by the same client. But how do we know which client is the right one? To solve this we will make use of the default “ws2007HttpBinding’s” binding configuration, which uses Windows Integrated Security and transmits the user’s credentials to the WCF service by default. In other words, we will use each user’s identity as a key for saving and retrieving it’s shopping cart state.

In Visual Studio open the IShoppingCartService.cs file and add a using statement to support serializing the “ShoppingCartItem” class. Also, add a [Serializable] attribute above that class and make class public.

using System.Xml.Serialization;

namespace ShoppingCartService
{
    // Shopping cart item
    [Serializable]
    public class ShoppingCartItem
    {
        public string ProductNumber { get; set; }
        public string ProductName { get; set; }
        public decimal Cost { get; set; }
        public int Volume { get; set; }
    }

    [ServiceContract(Namespace = "http://adventure-works.com/2010/06/04", Name = "ShoppingCartService")]
    public interface IShoppingCartService
    {
        [OperationContract(Name = "AddItemToCart")]
        bool AddItemToCart(string productNumber);

        [OperationContract(Name = "RemoveItemFromCart")]
        bool RemoveItemFromCart(string productNumber);

        [OperationContract(Name = "GetShoppingCart")]
        string GetShoppingCart();

        [OperationContract(Name = "Checkout")]
        bool Checkout();
    }
}

Change to the Service implementation file, “ShoppingCartService.cs”, add two using statements for supporting serializing and IO operations. I added a method to serialize and save the shoppingCart list variable, to an .xml file named with the client’s user name. If user’s identity name has invalid characters like “/” (often come from domain – domain/username) I need to replace them with a “!”.

using System.Xml.Serialization;
using System.IO;

namespace ShoppingCartService
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class ShoppingCartServiceImpl : IShoppingCartService
    {
        private void saveShoppingCart()
        {
            string userName = ServiceSecurityContext.Current.PrimaryIdentity.Name;
            foreach (char badChar in Path.GetInvalidFileNameChars())
            {
                userName = userName.Replace(badChar, '!');
            }
            string fileName = userName + ".xml";
            TextWriter writer = new StreamWriter(fileName);
            XmlSerializer ser = new XmlSerializer(typeof(List<ShoppingCartItem>));
            ser.Serialize(writer, shoppingCart);
            writer.Close();
        }

        private List<ShoppingCartItem> shoppingCart = new List<ShoppingCartItem>();
        //Other code omitted

Under this private method add a new one, for retrieving the ShoppingCart item list this time. It’s easy to understand what the method is doing. Get user’s name, see if an username.xml file exists and if so, retrieve the shoppingCart by deserializing it.

private void restoreShoppingCart()
        {
            string userName = ServiceSecurityContext.Current.PrimaryIdentity.Name;
            foreach (char badChar in Path.GetInvalidFileNameChars())
            {
                userName = userName.Replace(badChar, '!');
            }
            string fileName = userName + ".xml";
            if (File.Exists(fileName))
            {
                TextReader reader = new StreamReader(fileName);
                XmlSerializer ser = new XmlSerializer(typeof(List<ShoppingCartItem>));
                shoppingCart = (List<ShoppingCartItem>)ser.Deserialize(reader);
                reader.Close();
            }
        }

Now we need to modify our operation implementations to save and retrieve the shoppingCart when necessary. Starting with the “AddItemToCart” add a restoreShoppingCart call before searching the shopping cart (so it sees the latest version of it). Then add a saveShoppingCart call at the time the item volume is incremented (item was found in shopping cart) and a call after an item have been added to the cart (item added for first time).

public bool AddItemToCart(string productNumber)
        {
            try
            {
                restoreShoppingCart();
                ShoppingCartItem item = find(shoppingCart, productNumber);
                // If so, then simply increment the volume
                if (item != null)
                {
                    item.Volume++;
                    saveShoppingCart();
                    return true;
                }
                else
                {
                    using (AdventureWorks2012Entities database = new AdventureWorks2012Entities())
                    {
                        // Retrieve the details of the selected product
                        Product product = (from p in database.Products
                                           where string.Compare(p.ProductNumber,
                                           productNumber) == 0
                                           select p).First();
                        ShoppingCartItem newItem = new ShoppingCartItem
                        {
                            ProductNumber = product.ProductNumber,
                            ProductName = product.Name,
                            Cost = product.ListPrice,
                            Volume = 1
                        };
                        // Add the new item to the shopping cart
                        shoppingCart.Add(newItem);
                        saveShoppingCart();
                        // Indicate success
                        return true;
                    }
                }
            }
            catch
            {
                // If an error occurs, finish and indicate failure
                return false;
            }
        }

In the same direction add a RestoreShoppingCart() and a SaveShoppingCart() call in the RemoveItemFromCart() operation implementation when necessary. Restore before searching the item and save after removed it.

public bool RemoveItemFromCart(string productNumber)
        {
            restoreShoppingCart();
            ShoppingCartItem item = find(shoppingCart, productNumber);
            if (item != null)
            {
                item.Volume--;
                if (item.Volume == 0)
                {
                    shoppingCart.Remove(item);
                }
                saveShoppingCart();
                return true;
            }
            return false;
        }

In the GetShoppingCart operation implementation add a RestoreShoppingCart() call before iterating the items in the shopping cart.

public string GetShoppingCart()
        {
            string formattedContent = String.Empty;
            decimal totalCost = 0;
            restoreShoppingCart();
            foreach (ShoppingCartItem item in shoppingCart)
            {
                string itemString = String.Format( "Number: {0}\tName: {1}\tCost: {2:C}\tVolume: {3}",
                item.ProductNumber, item.ProductName, item.Cost,item.Volume);
                totalCost += (item.Cost * item.Volume);
                formattedContent += itemString + "\n";
            }
            string totalCostString = String.Format("\nTotalCost: {0:C}", totalCost);
            formattedContent += totalCostString;
            return formattedContent;
        }

Now it’s time to test our implementation but before doing it, make sure you have selected the “PerCall” Instance Context Mode option for the implementation class.

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
    public class ShoppingCartServiceImpl : IShoppingCartService

Build the solution. Create a new proxy class in the same way we did in previous posts. Copy and paste the new “ShoppingCartServiceProxy.cs” file to both of the client’s projects. Make sure you have reserved the http://+:9000/ address to your username account. For these operations you can go and read the previous posts. Run the solution as it is.

managestate1

Can you see the amazing difference? Despite using the “PerCall” option for service instance, the state of the shopping cart is maintained for the current user. First I pressed enter for the first client who creates two proxys if you recall, and add two kind of items in the cart. Notice that the second proxy has access to the same shopping cart item with the first. This is because WCF service calls are invoked by the same Windows user account, in my case “developer-pc/developer”. The second client project, also has access to that shopping cart since his proxy uses the same Windows User account credentials. So all calls use the same developer-pc!developer.xml file located at the bin/Debug folder of the host project.

managestate2

Open and see the content of that file. This is the State of the shopping cart variable.

<?xml version="1.0" encoding="utf-8"?>
<ArrayOfShoppingCartItem xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <ShoppingCartItem>
    <ProductNumber>SA-M687</ProductNumber>
    <ProductName>HL Mountain Seat Assembly</ProductName>
    <Cost>196.9200</Cost>
    <Volume>6</Volume>
  </ShoppingCartItem>
  <ShoppingCartItem>
    <ProductNumber>SA-M198</ProductNumber>
    <ProductName>LL Mountain Seat Assembly</ProductName>
    <Cost>133.3400</Cost>
    <Volume>3</Volume>
  </ShoppingCartItem>
</ArrayOfShoppingCartItem>

You wanna see something more interesting? Try and call the WCF service from the new client project using another windows account. In my case I will modify the proxy credentials of that client to user a user account with Username:Bert and Pass:password. Run the solution. What we expect is the new client use different shopping cart variable, which is restored by a different .xml file named “developer-pc/Bert.xml”. Before running the solution you maybe want to delete the old .xml file so you can have an empty shopping cart.

class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Press ENTER when the service has started");
            Console.ReadLine();
            try
            {
                // Connect to the ShoppingCartService service
                ShoppingCartServiceClient proxy = new  ShoppingCartServiceClient("WS2007HttpBinding_IShoppingCartService");
                proxy.ClientCredentials.Windows.ClientCredential.UserName = "Bert";
                proxy.ClientCredentials.Windows.ClientCredential.Password = "password";
                proxy.AddItemToCart("SA-M687");
                proxy.AddItemToCart("SA-M687");
                // Add a mountain seat assembly to the shopping cart
                proxy.AddItemToCart("SA-M198");
                // Query the shopping cart and display the result
                string cartContents = proxy.GetShoppingCart();
                Console.WriteLine("Second client starting..");
                Console.WriteLine(cartContents);
                // Disconnect from the ShoppingCartService service
                proxy.Close();
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception: {0}", e.Message);
            }
            Console.WriteLine("Press ENTER to finish");
            Console.ReadLine();
        }
    }

managestate3

That’s it. I hope you enjoyed it.

Advertisements


Categories: WCF

Tags:

1 reply

  1. Hello to every one, because I am actually eager of reading this
    web site’s post to be updated on a regular basis. It includes nice stuff.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Chara Plessa

The purpose of this blog is to broaden my education, promote experimentation and enhance my professional development. Albert Einstein once said that “If you can’t explain it simply, you don’t understand it well enough” and I strongly believe him!

chsakell's Blog

Anything around ASP.NET MVC,WEB API, WCF, Entity Framework & AngularJS

Kumikoro

A Front End Developer's Blog

Muhammad Hassan

Full Stack Developer | ASP.NET | MVC | WebAPI | Advanced Javascript | AngularJS | Angular2 | C# | ES6 | SQL | TypeScript | HTML5 | NodeJS, MS candidate @LUMS, Grad & EX-Adjunct Faculty @NUCES-FAST, seasonal blogger & open-source contributor. Seattle, WA.

Software Engineering

Web development

IEvangelist

.NET, ASP.NET, C#, MVC, TypeScript, AngularJS

leastprivilege.com

Dominick Baier on Identity & Access Control

Happy DotNetting

In Love with Technology

Knoldus

Knols of experience to your advantage

knowshnet

Search - Read - Request - Share

Rahul's space

Learn, Share and Grow with me !

Dhananjay Kumar

Developer Evangelist @Infragistics | MVP @Microsoft |

SQL Authority with Pinal Dave

SQL Server Performance Tuning Expert

Conficient Blog

Random bits of tech from @conficient

Code! Code! Code!

SOLID & KISS

Code Wala

Designing and coding

Microsoft Mentalist

A way to start with Microsoft Technologies

Tony Sneed's Blog

A glimpse into the lives of Tony & Zuzana Sneed

Sriramjithendra Nidumolu

Personal Notes of Sriramjithendra

%d bloggers like this: