Click here to Skip to main content
Click here to Skip to main content

Stateful Web Services using WCF leveraging ASP.NET infrastructure with BasicHttpBinding

, 20 Jul 2010
Rate this:
Please Sign up or sign in to vote.
This article explains how to manage state in Web Services using WCF by leveraging ASP.NET infrastructure with BasicHttpBinding.

Introduction

This article explains how to develop a stateful WCF service leveraging the ASP.NET infrastructure, i.e., the ASP.NET HTTP Pipeline, using BasicHttpBinding, using an example of a miniature shopping cart.

Background

The background knowledge expected is C#, ASP.NET, and WCF.

Using the Code

We shall follow a contract-first approach quite similar to an SOA implementation for this example.

  1. Create a blank solution named StateManagementWCF.
  2. Add a Class Library project named OrderServiceContract.
  3. Rename Class1.cs to IOrderService.cs and insert the following code therein:
  4. using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.ServiceModel.Activation;
    using System.ServiceModel.Description;
    using System.ServiceModel.Web;
    using System.Text;
    namespace OrderServiceContract
    {
        [ServiceContract(Namespace = "OrderServiceContract", 
         SessionMode = SessionMode.Allowed)]
        public interface IOrderService
        {
            [OperationContract]
            void StartPurchase();
    
            [OperationContract]
            string PlaceOrder(Item item);
    
            [OperationContract]
            string MakePayment(decimal amount);
    
            [OperationContract]
            string ShipOrder(string address);
    
            [OperationContract]
            void EndPurchase();
    
            // TODO: Add your service operations here
        }
    
        // Use a data contract as illustrated in the sample
        // below to add composite types to service operations.
        [DataContract]
        public class Item
        {
            [DataMember]
            public string ItemName { get; set; }
    
            [DataMember]
            public decimal Price { get; set; }
        }
    }

    Here, you first declare and define a service contract with SessionMode as Allowed, and then a data contract for the composite object item. With the StartPurchase operation, the buyer shall start the purchasing session. With the PlaceOrder operation, the buyer shall place order for an item. This operation can be called multiple times in the session to purchase multiple items. In the service instance itself, the total outstanding amount for the items purchased would be calculated and remembered. Then the buyer would make payments by calling the MakePayment operation. The cumulative payment made shall be remembered by the service instance each time. Finally, when the buyer calls ShipOrder, the service instance checks whether full payment has been made or not. In the end, the session is terminated by a call to the EndPurchase operation. The schema supports session with the help of the SessionMode attribute.

  5. Now add another project of type WCF Service Application and name it as UsageService. Add a reference to the System.ServiceModel assembly. Rename the service to OrderService.svc.
  6. Have the following code in the OrderService.svc.cs file:
  7. using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Runtime.Serialization;
    using System.ServiceModel;
    using System.ServiceModel.Channels;
    using System.ServiceModel.Description;
    using System.ServiceModel.Activation;
    using System.ServiceModel.Web;
    using System.Text;
    
    namespace UsageService
    {
        [AspNetCompatibilityRequirements(RequirementsMode = 
             AspNetCompatibilityRequirementsMode.Allowed)] 
        public class OrderService : OrderServiceContract.IOrderService, IDisposable
        {
            private decimal TotalAmount { get; set; }
            private decimal PaymentReceived { get; set; }
            private bool TransactionStarted {get; set;}
    
            public OrderService()
            {
                TotalAmount = 0;
                PaymentReceived = 0;
                TransactionStarted = false;
            }
    
            public void StartPurchase()
            {
                HttpContext.Current.Session["TransactionStarted"] = true;
                HttpContext.Current.Session["TotalAmount"] = 0;
                HttpContext.Current.Session["PaymentReceived"] = 0;
            }
    
            public string PlaceOrder(OrderServiceContract.Item item)
            {
                if (Convert.ToBoolean(HttpContext.Current.Session["TransactionStarted"]))
                {
                    HttpContext.Current.Session["TotalAmount"] = 
                      Convert.ToDecimal(
                      HttpContext.Current.Session["TotalAmount"]) + item.Price;
                    return "Order placed for item " + item.ItemName + 
                      " and total outstanding amount is $" + 
                      HttpContext.Current.Session["TotalAmount"].ToString();
                }
                return "Shopping session not yet started";
            }
    
            public string MakePayment(decimal amount)
            {
                if (Convert.ToBoolean(HttpContext.Current.Session["TransactionStarted"]))
                {
                    HttpContext.Current.Session["PaymentReceived"] = 
                      Convert.ToDecimal(
                      HttpContext.Current.Session["PaymentReceived"]) + amount;
                    return "Payment made of amount USD " + 
                       HttpContext.Current.Session["PaymentReceived"].ToString() + 
                       " and amount remaining to be paid is $" + 
                       ((Convert.ToDecimal(HttpContext.Current.Session["TotalAmount"])) - 
                       (Convert.ToDecimal(
                       HttpContext.Current.Session["PaymentReceived"]))).ToString();
                }
                return "Shopping session not yet started";
            }
    
            public string ShipOrder(string address)
            {
                if (Convert.ToBoolean(HttpContext.Current.Session["TransactionStarted"]))
                {
                  if ((Convert.ToDecimal(HttpContext.Current.Session["TotalAmount"])) <= 
                      (Convert.ToDecimal(HttpContext.Current.Session["PaymentReceived"])))
                  {
                      return "Ordered items would be reaching" + 
                             " at your doorstep soon. Thanks";
                  }
                  return "Please pay the full amount in advance in order to enable " + 
                      "us ship your items, the outstanding amount is $" + 
                      ((Convert.ToDecimal(HttpContext.Current.Session["TotalAmount"])) - 
                      (Convert.ToDecimal(
                      HttpContext.Current.Session["PaymentReceived"]))).ToString();
                }
                return "Shopping session not yet started";
            }
    
            public void EndPurchase()
            {
                if (Convert.ToBoolean(HttpContext.Current.Session["TransactionStarted"]))
                {
                    HttpContext.Current.Session["TransactionStarted"] = false;
                }
            }
    
            public void Dispose()
            {
            }
        }
    }

    Here, the OrderService class implements the IOrderService and IDisposable interfaces. The class is decorated with the attribute:

    [AspNetCompatibilityRequirements(RequirementsMode = 
            AspNetCompatibilityRequirementsMode.Allowed)]

    This makes the WCF service leverage the existing ASP.NET HTTP pipeline and thereby get a sort of "license" to use the HttpContext.Current.Session object to store (remember) stateful data. During the StartPurchase operation, we initialize the data in session variables, thereby setting up the session. In every other operation, we check whether the session is started or not by checking the TransactionStarted boolean variable which is stored in a session variable (and therefore remembered across calls).

  8. Now we shall modify the web.config. The modified web.config is presented below:
  9. <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
      <system.web>
        <compilation debug="true"/>
        <sessionState cookieless="false" mode="InProc"/>
      </system.web>
      <system.serviceModel>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
        <services>
          <service name="UsageService.OrderService"   
                   behaviorConfiguration="UsageService.OrderServiceBehavior">
            <host>
              <baseAddresses>
                <add baseAddress="http://localhost/UsageService" />
              </baseAddresses>
            </host>
            <endpoint address="" binding="basicHttpBinding" 
                      bindingConfiguration="OrderBinding"  
                      contract="OrderServiceContract.IOrderService" />
            <endpoint address="mex" binding="mexHttpBinding" 
                      contract="IMetadataExchange" />
          </service>
        </services>
        <behaviors>
          <serviceBehaviors>
            <behavior name="UsageService.OrderServiceBehavior">
              <serviceMetadata httpGetEnabled="true" />
              <serviceDebug includeExceptionDetailInFaults="true"/>
            </behavior>
          </serviceBehaviors>
        </behaviors>
        <bindings>
          <basicHttpBinding>
            <binding name="OrderBinding" allowCookies="true">
              <security mode="None" />
            </binding>
          </basicHttpBinding>
        </bindings>
      </system.serviceModel>
      <system.webServer>
        <modules runAllManagedModulesForAllRequests="true" />
            <directoryBrowse enabled="true" />
      </system.webServer>
    </configuration>

    Here, the first thing we do is set the sessionState element's cookieless attribute to false, thereby supporting session cookies and specifying how the state would be maintained. We choose InProc (in-memory session data store) for now just for simplicity sake, as otherwise we would get diverted from our actual discussion to state management techniques in ASP.NET.

    <system.web>
        <compilation debug="true"/>
        <sessionState cookieless="false" mode="InProc"/>
    </system.web>

    Next, we set the ASP.NET compatibility as follows:

    <system.serviceModel>
        <serviceHostingEnvironment aspNetCompatibilityEnabled="true"/>
       ...
       ...

    Here, we enable the aspNetCompatibilityEnabled attribute of serviceHostingEnvironment. So IIS hosts the service, and IIS and the ASP.NET integrated modules and handlers provide the hosting environment. Next, we can observe that we have used basicHttpBinding here, which actually mimics the erstwhile ASP.NET Web Services. Now, in the binding configuration, we allow cookies:

    <bindings>
      <basicHttpBinding>
        <binding name="OrderBinding" allowCookies="true">
          <security mode="None" />
        </binding>
      </basicHttpBinding>
    </bindings>

    The reason for doing this is that when the client first makes a call to the service through a proxy, a service instance gets created, a session is started, and a session cookie is returned to the client, which the client has to submit to the server with each successive service call in the session. In the session cookie, the session ID is stored, which when the service receives with each successive service call, it understands which client is calling it and what the session data are for that particular client session, given that there might be many such clients calling those service operations simultaneously. The session ID is unique for each session and distinct for all clients.

  10. Next, we create a virtual directory called UsageService in IIS and test the service by browsing OrderService.svc.
  11. Next, we add a Windows Forms Application project to the solution named as ShoppingClient, and add a reference to System.ServiceModel to it.
  12. Now we add a service reference to the ShoppingClient for the endpoint: http://localhost/UsageService/OrderService.svc.
  13. We then design the UI for the form, and finally add the following code in ShoppingForm.cs:
  14. public partial class ShoppingForm : Form
    {
        private List<OrderServiceReference.Item> itemList = null;
        private OrderServiceReference.OrderServiceClient clientService = null;
    
        public ShoppingForm()
        {
            InitializeComponent();
            itemList = new List<OrderServiceReference.Item>();
        }
    
        private void AddItemsToList()
        {
            OrderServiceReference.Item itm = new OrderServiceReference.Item() 
                           { ItemName = "Bag", Price = (decimal)10.70 };
            itemList.Add(itm);
            itm = new OrderServiceReference.Item() 
                  { ItemName = "Boot", Price = (decimal)11.30 };
            itemList.Add(itm);
            itm = new OrderServiceReference.Item() 
                  { ItemName = "Basket", Price = (decimal)10.00 };
            itemList.Add(itm);
            itm = new OrderServiceReference.Item() 
                  { ItemName = "Box", Price = (decimal)20.07 };
            itemList.Add(itm);
            itm = new OrderServiceReference.Item() 
                  { ItemName = "Bat", Price = (decimal)1.93 };
            itemList.Add(itm);
        }
    
        private void ShoppingForm_Load(object sender, EventArgs e)
        {
            try
            {
                clientService = new OrderServiceReference.OrderServiceClient();
                clientService.StartPurchase();
                this.AddItemsToList();
            }
            catch (Exception ex)
            {
                txtMessage.Clear();
                txtMessage.Text = ex.Message + "\n" + ex.Source + "\n" + 
                                  ex.StackTrace + "\n" + ex.TargetSite;
            }
        }
    
        private void btnExit_Click(object sender, EventArgs e)
        {
            Application.Exit();
        }
    
        private void btnMakePayment_Click(object sender, EventArgs e)
        {
            txtMessage.Clear();
            if (txtAmount.Text == String.Empty)
            {
                MessageBox.Show("Please enter amount first");
                return;
            }
            txtMessage.Text = clientService.MakePayment(
                                Convert.ToDecimal(txtAmount.Text.Trim()));
            txtAmount.Clear();
        }
    
        private void btnPurchaseBag_Click(object sender, EventArgs e)
        {
            txtMessage.Clear();
            txtMessage.Text = clientService.PlaceOrder(itemList[0]);
        }
    
        private void btnPurchaseBoot_Click(object sender, EventArgs e)
        {
            txtMessage.Clear();
            txtMessage.Text = clientService.PlaceOrder(itemList[1]);
        }
    
        private void btnPurchaseBasket_Click(object sender, EventArgs e)
        {
            txtMessage.Clear();
            txtMessage.Text = clientService.PlaceOrder(itemList[2]);
        }
    
        private void btnPurchaseBox_Click(object sender, EventArgs e)
        {
            txtMessage.Clear();
            txtMessage.Text = clientService.PlaceOrder(itemList[3]);
        }
    
        private void btnPurchaseBat_Click(object sender, EventArgs e)
        {
            txtMessage.Clear();
            txtMessage.Text = clientService.PlaceOrder(itemList[4]);
        }
    
        private void btnShipOrder_Click(object sender, EventArgs e)
        {
            txtMessage.Clear();
            txtMessage.Text = clientService.ShipOrder("DF-I, B-2/4, " + 
               "PURBA ABASAN, 1582/1 RAJDANGA MAIN ROAD, KOLKATA - 700107, INDIA");
        }
    }

    Here, we maintain a list for items ordered and fill the same in a method called AddItemsToList(), just to keep things simple. Now, during Form_Load, we call the StartPurchasing() operation to start the session with the service. We call the PlaeOrder(item) method to place orders, and the MakePayment(amount) method to make payments, and finally the ShipOrder(address) method to request the service to ship the ordered items. We see that we are doing nothing about state management at the client code, because the trick is done in the app.config.

  15. The App.config file (after modification) is presented as follows:
  16. <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
       <system.serviceModel>
            <bindings>
                <basicHttpBinding>
                    <binding name="BasicHttpBinding_IOrderService" 
                           closeTimeout="00:01:00"
                          openTimeout="00:01:00" receiveTimeout="00:10:00"    
                       sendTimeout="00:01:00"
                        allowCookies="true" bypassProxyOnLocal="false" 
                      hostNameComparisonMode="StrongWildcard"
                        maxBufferSize="65536" maxBufferPoolSize="524288" 
                           maxReceivedMessageSize="65536"
                        messageEncoding="Text" textEncoding="utf-8" 
                           transferMode="Buffered"
                        useDefaultWebProxy="true">
                        <readerQuotas maxDepth="32" maxStringContentLength="8192"
                             maxArrayLength="16384"
                            maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                        <security mode="None">
                            <transport clientCredentialType="None" 
                             proxyCredentialType="None"      realm="" />
                            <message clientCredentialType="UserName" 
                             algorithmSuite="Default" />
                        </security>
                    </binding>
                </basicHttpBinding>
            </bindings>
            <client>
                <endpoint address="http://kummu-pc/UsageService/OrderService.svc"
                    binding="basicHttpBinding" 
                   bindingConfiguration="BasicHttpBinding_IOrderService"
                    contract="OrderServiceReference.IOrderService" 
                 name="BasicHttpBinding_IOrderService" />
            </client>
        </system.serviceModel>
    </configuration>

    Here we have set allowCookies to true for the binding. The proxy automatically handles the session cookie returned by the service and submits the session cookie to the service each time a service call is made thereafter. All this plumbing is done transparent to the developer.

Points of Interest

Now, in order to improve the scalability of the application, the session state in web.config should be stored in SQL Server by setting mode="SqlServer" instead of mode = "InProc". The database that gets created automatically is ASPNETDB. Another attribute to be specified when mode="SqlServer" is the connectionString. Normally, for high availability, the SQL Servers are active-passive clustered and hence, the session data is not lost due to any failures. Also, transport level security could be implemented using HTTPS instead of HTTP. In that case, a certificate could be built using the makecert utility and placed in a certificate store with public key distributed to the clients. At the message level, encoding could be done to have message level security. Also, last but not the least, the credentials can be configured for basicHttpBinding and that can leverage the Membership API and Roles API of the ASP.NET HTTP pipeline processing features.

History

  • Posted on 20 July 2010.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

AmbarRay
Architect Pramerica Systems Ireland
India India
CORE COMPETENCIES
 
 Design and Architecture for Microsoft SOA implementations using BizTalk, WCF, WF, SQL Server Integration Services & ESB Toolkit.
 
 Web Application design and implementation using ASP.NET MVC with jQuery & Entity Framework with LINQ for persistence layer.
 
 Designing and developing desktop applications using WPF, WinForms.
 
 SQL Server Database design & programming
 
EXPERIENCE SUMMARY
 
 Eleven years total, out of which 04 years as architect, 07 years as designer and developer in various Microsoft technologies like WinForms, ASP.NET WebForms, ASP.NET MVC, BizTalk, WCF, WF, WPF, SQL Server, LINQ, EF etc. Worked in various roles mainly architect, designer, team leader and developer.
 
 Hands on experience with ASP.NET web applications (both MVC and Web Forms) and desktop based applications as well as smart client applications using latest technologies harnessing .NET Framework 4.0, C# 4.0, ASP.NET 4.0, WCF, WF, WPF and LINQ.
 
 Hands-on working experience in Application integration, business process management and service oriented applications using BizTalk Server 2010, ESB Toolkit 2.1, WCF 4.0 services and SQL Server 2008 Integration Services.
 
 Thorough working knowledge of OOAD and agile / incremental development process.
 
 Experience in leading team of developers for on schedule project delivery with quality.
 
 Experience with low level programming like embedded C, C++ as well as systems programming on unix platform.
REMARKABLE PROFESSIONAL ACHIEVEMENTS
 
 Got Microsoft Community Contributor Award in year 2011 with MCC ID# 4034514.
 
 Published article in MSDN Magazine Feb 2011 edition on MDM with F#: http://msdn.microsoft.com/en-us/magazine/gg598923.aspx
http://www.codeproject.com/News/14767/Pattern-Matching-Database-Records-with-Fsharp.aspx
 
 Published highly popular article on BizTalk in www.dotnetcurry.com
http://www.dotnetcurry.com/ShowArticle.aspx?ID=683
 
 Umpteen blogs in BizTalk server forums and ESB toolkit forums.

Comments and Discussions

 
Questiontks!!!! PinmemberMember 415900230-May-12 15:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.140709.1 | Last Updated 20 Jul 2010
Article Copyright 2010 by AmbarRay
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid