Managing State and Service Instances in a WCF service – The case of a ShoppingCart : Part 1

We have seen in previous posts how to host and consume WCF services where the client used to invoke some operations of the service. These operations though, were totally independent and no state was needed to be kept. The client invokes the desired operation, for example GetProduct, and the service just returns the result back to him. If client decide to call one more time the same operation, the first one will have no impact on the second one. Consider though, the case of an e-shop website, where users add items to cart via a webservice, calling an “AddToCart” operation several times. When the user decides to check out he wants to see what items were added to cart. So how can this be implemented? Who’s side is the one who keeps state of what items are being added to cart? Who keeps the state of the cart? The answer is that both client and the WCF service can maintain user’s data during a session. The client could store information using cookies but this raise security issues. We are going to see how the WCF service can maintain the shopping cart contents through a structure.

Start by creating a blank solution and add a WCF Service library named “ShoppingCartService”. Rename the IService.cs and Service.cs file to IShoppingCartService.cs and ShoppingCartService.cs respectively. This will be our service project which will support operations such as “AddItemToCart” or “RemoveItemToCart”. We ‘ll also need access the AdventureWork database so create an EntityModel pointing to that database, and add the Product and ProductInventory tables to it. Creating EntityModel is out of scope of this post and I assume you know how to do it. Below you will find the code both for IShoppingCartService.cs and ShoppingCartService.cs files.

shopservice

    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();
    }
}

 

namespace ShoppingCartService
{
    public class ShoppingCartServiceImpl : IShoppingCartService
    {
        private List shoppingCart = new List();

        private ShoppingCartItem find(List shoppingCart, string productNumber)
        {
            foreach (ShoppingCartItem item in shoppingCart)
            {
                if (string.Compare(item.ProductNumber, productNumber) == 0)
                {
                    return item;
                }
            }
            return null;
        }

        public bool AddItemToCart(string productNumber)
        {
            try
            {
                ShoppingCartItem item = find(shoppingCart, productNumber);
                // If so, then simply increment the volume
                if (item != null)
                {
                    item.Volume++;
                    return true;
                }
                // Otherwise, retrieve the details of the product from the database
                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();
                        // Create and populate a new shopping cart item
                        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);
                        // Indicate success
                        return true;
                    }
                }
            }
            catch
            {
                // If an error occurs, finish and indicate failure
                return false;
            }
        }

        public bool RemoveItemFromCart(string productNumber)
        {
            ShoppingCartItem item = find(shoppingCart, productNumber);
            // If so, then decrement the volume
            if (item != null)
            {
                item.Volume--;
                // If the volume is zero, remove the item from the shopping cart
                if (item.Volume == 0)
                {
                    shoppingCart.Remove(item);
                }
                // Indicate success
                return true;
            }
            // No such item in the shopping cart
            return false;
        }

        public string GetShoppingCart()
        {
            string formattedContent = String.Empty;
            decimal totalCost = 0;
            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;
        }

        public bool Checkout()
        {
            // Not currently implemented - just return true
            return true;
        }
    }
}

You will notice that the service make use of a structure “ShoppingCartItem” for every item the user wants to add to the cart. Every instance of the service is assigned a list of “ShoppingCartItem” so when the user adds items, these are added to the list. When the user wants to remove an item, this item is removed from the pre-mentioned list. The all functionality is simple. To add an item, we first retrieve it from the database passing the ProductNumber as a parameter and then create a new instance of ShoppingCartItem, assigning the values just retrieved from database. Build the solution and continue by creating a new project in the solution. This will be our host project, a console C# application, named “ShoppingCartHost”. Right click the new project and add references to the “ShoppingCartService” project plus the System.ServiceModel and System.Data.Entity assemblies. Configure the App.config file to host the service as follow. Be careful to modify the “connectionStrings” element due to yours implementation.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
  <connectionStrings>
    <add name="AdventureWorks2012Entities" connectionString="metadata=res://*/ProductsEntityModel.csdl|res://*/ProductsEntityModel.ssdl|res://*/ProductsEntityModel.msl;provider=System.Data.SqlClient;provider connection string=&quot;data source=developer-pc;initial catalog=AdventureWorks2012;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework&quot;" providerName="System.Data.EntityClient" />
  </connectionStrings>
  <system.serviceModel>
    <services>
      <service name="ShoppingCartService.ShoppingCartServiceImpl">
        <endpoint address="http://localhost:9000/ShoppingCartService/ShoppingCartService.svc"
          binding="ws2007HttpBinding" bindingConfiguration="" contract="ShoppingCartService.IShoppingCartService" />
      </service>
    </services>
  </system.serviceModel>
</configuration>

Notice that the service will be host by the console application at the http://localhost:9000/ShoppingCartService/ShoppingCartService.svc address. In order for the client application to use that http port you need first to reserve it. Run the following command in the “Developer Command Promt for VS2012”, which relies under the Developer Tools folder inside the Visual Studio 2012 folder. Make sure you enter your username.

netsh http add urlacl url=http://+:9000/ user=UserName

Modify the Program.cs file of the host Console application to start the service when it runs.

using System.ServiceModel;

namespace ShoppingCartHost
{
    class Program
    {
        static void Main(string[] args)
        {
            ServiceHost host = new ServiceHost(typeof(ShoppingCartService.ShoppingCartServiceImpl));
            host.Open();
            Console.WriteLine("Service running");
            Console.WriteLine("Press ENTER to stop the service");
            Console.ReadLine();
            host.Close();
        }
    }
}

Next, add a new Console application which will be our client. Name it “ShoppingCartClient” (weird ah?). We need to add a proxy class which will be able to call the service operations. Also we need to configure the App.config file and add the right endpoint. For the proxy class, open again the “Developer Command Promt for VS2012”, navigate to the Service’s project /bin/debug folder and run the following commands.

svcutil ShoppingCartService.dll

svcutil /namespace:*,ShoppingCartClient.ShoppingCartService adventure-works.com.2010.06.04.wsdl *.xsd /out:ShoppingCartServiceProxy.cs

The ShoppingCartServiceProcy.cs file created will be our proxy implementation. Simply copy and paste in your client project.

shoppingCart

Following edit the App.config of the client’s project and add some code to the Main method, to actually call the service, add three items and then display them from the Shopping Cart. Make sure you have added a System.ServiceModel reference to the client project.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
    </startup>
    <system.serviceModel>
        <client>
            <endpoint address="http://localhost:9000/ShoppingCartService/ShoppingCartService.svc"
                binding="ws2007HttpBinding" bindingConfiguration="" contract="ShoppingCartClient.ShoppingCartService.ShoppingCartService"
                name="WS2007HttpBinding_IShoppingCartService" kind="" endpointConfiguration="">
            </endpoint>
        </client>
    </system.serviceModel>
</configuration>
using System.ServiceModel;
using ShoppingCartClient.ShoppingCartService;

namespace ShoppingCartClient
{
    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");
                // Add the same Item two times
                proxy.AddItemToCart("SA-M687");
                proxy.AddItemToCart("SA-M687");
                // Add a new Item to cart
                proxy.AddItemToCart("SA-M198");
                // Query the shopping cart and display the result
                string cartContents = proxy.GetShoppingCart();
                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();
        }
    }
}

That’s it. Right click the solution and select Start up projects. Choose the host and the client projects and set as Action Start. Build the solution and Run Without Debugging. Press enter on the client console application and you will see that items actually were added to the Cart and then retrieved back.

startup

Notice that the first two items, added with the same ProductNumber, that is the user want to buy two of them. So, since at the second call, the particular item were already on the cart, the volume was just increased by one. But what if one other client, consume the same service at the same time and add the same item? Was he going to order 3 Items instead of one? We are going to answer that in later post where we are going to continue the discussion on how to manage State and Instances of a WCF service

runcartserv



Categories: WCF

4 replies

  1. Hello I am refreshing my knowledge of WCF lately and have really enjoyed your posts! One question though – aren’t services by default going to lose your shoppingCart List? Don’t you need some sort of service attribute to maintain state between calls, or is this a feature specific to the ws2007HttpBinding you’ve specified? Thanks!

    • Very good question actually. For now just keep this in mind: The service with the above simple implementation creates an instance of a ShoppingCart list (private instance variable) when an instance of the service is itself created by the host. This instance is only destroyed after the client closes the connection which means that it can hang around between calls (from same client). If another client application calls the WCF service, by default a new Service instance will be created and will maintain a new shoppingCart list for serving that client. But this is configurable. As you mentioned you can use specific attributes to manage instances and state, for example you could use only one instance of the service for managing all client requests or create a new instance every time an operation of the service was consumed. We are going to see how these work in another post, as soon as possible i hope!

Trackbacks

  1. Managing State and Service Instances in a WCF service – The case of a ShoppingCart : Part 2 | chsakell's Blog
  2. Managing State and Service Instances in a WCF service : Part 3 – Maintaining State with the PerCall Instance Context Mode | chsakell's Blog

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 )

Connecting to %s

%d bloggers like this: