Wednesday, December 14, 2011

Session State in a Web Service


Using Session State in a Web Service
Usually, when you think of a Web Service, you think …make the call, get the response, and get on with the task at hand. These "one shot" calls are the norm in Web Services but there may be times when you need a little more. You may need the Web Service to remember states between calls.
As an example, I wrote a Web Service that had to perform a lengthy operation. I didn't want to lock up the client by making a synchronous call, so once the job was started, the call returned. Every few seconds, on a timer, the client would call a GetStatus function in the Web Service to get the status of the running job. When the status was retrieved, a progress bar on the client was updated. It worked well. 
There is support for using Session State in Web Services but it doesn't work "out-of-the-box".
This article assumes you know how to create and call a Web Service.
First we need a Web Service. I created a basic Web Service and then modified the supplied Hello World function that is created by default.
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Services;

namespace DemoWebService
{
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]

    public class MyDemo : System.Web.Services.WebService
    {
        [WebMethod (EnableSession = true)]
        public string HelloWorld()
        {
            // get the Count out of Session State
            int? Count = (int?)Session["Count"];

            if (Count == null)
                Count = 0;

            // increment and store the count
            Count++;
            Session["Count"] = Count;

            return "Hello World - Call Number: " + Count.ToString();
        }
    }
}
Note: The key thing to do is add (EnableSession = True) to the WebMethod tag. The rest of the code simply keeps track of how many times each client has called the Web Service method.
Side Note: I am using a Nullable Type to simplify the retrieving of Count from the Session collection.
Here's the old school way:
int Count;

if (Session["Count"] == null)
    Count = 0;
else
    Count = (int)Session["Count"];
Here's the new school way...Nullable Type...is better, yah?
int? Count = (int?)Session["Count"];

if (Count == null)
    Count = 0;
Ok, now the client. You've built the Web Service. You've added the Web Service to the client project (right-click the project and select "Add Web Reference…"). A proxy class has been created.
In a button click event, we instantiate the proxy class, call our function and update a label. This is going to be great…it's going to be so easy… 
protected void Button1_Click(object sender, EventArgs e)
{
    // instantiate the proxy and call our function
    localhost.MyDemo MyService = new localhost.MyDemo();

    Label1.Text += MyService.HelloWorld() + "<br />";
}
We click the button a few times, but instead counting, we get this:
Hello World - Call Number: 1
Hello World - Call Number: 1
Hello World - Call Number: 1 

Explanation: When an object is put into the Session collection, Asp.Net gives the caller an indentifying cookie behind the scenes…the SessionID. This is like when you check your coat at an expensive restaurant (I've heard) and you get a coat check ticket. When you go to get your coat back, you must have the ticket. So why isn't the proxy storing the SessionID as a cookie?
Key Point: The proxy generated doesn't have a CookieContainer.
Solution: Create a CookieContainer in the proxy (it already has a reference for it)
Here's the modified code:  
protected void Button1_Click(object sender, EventArgs e)
{
    // instantiate the proxy
    localhost.MyDemo MyService = new localhost.MyDemo();

    // create a container for the SessionID cookie
    MyService.CookieContainer = new CookieContainer();

    // call the Web Service function
    Label1.Text += MyService.HelloWorld() + "<br />";
}
All right, problem solved. This is great…it's going to be so easy… Fire up the web page, click the button a few times, and…
Hello World - Call Number: 1
Hello World - Call Number: 1
Hello World - Call Number: 1

Explanation: When the proxy went out of scope, it was uninstantiated and ceased to exist. The Cookie Container also ceased to exist. And our cookie, the Session Id…a.k.a. coat check ticket…was lost.
Key Point: The proxy has to remain in existence between calls to the Web Service.
Solution: Keep the proxy around. In a Windows Forms application this is easy. Make the proxy global or static…just don't put it on the stack. For a Web Application it's a bit trickier, we put the proxy into the Session collection. 
protected void Button1_Click(object sender, EventArgs e)
{
    localhost.MyDemo MyService;

    // try to get the proxy from Session state
    MyService = Session["MyService"] as localhost.MyDemo;

    if (MyService == null)
    {
        // create the proxy
        MyService = new localhost.MyDemo();

        // create a container for the SessionID cookie
        MyService.CookieContainer = new CookieContainer();

        // store it in Session for next usage
        Session["MyService"] = MyService;
    }

    // call the Web Service function
    Label1.Text += MyService.HelloWorld() + "<br />";
}  
All right, we hope the problem is solved because this is getting tiring. Fire up the web page, click the button a few times and…
Hello World - Call Number: 1
Hello World - Call Number: 2
Hello World - Call Number: 3

If you use "Add Web Service" instead of "Add Web Reference" in VS2008, the proxy generated works with out any fooling around.  However, the proxy can only be consumed by .Net 3.0 and higher applications


WCF Sessions
Whenever web developers hear the term session, we think of Http Session which is used to store data between multiple HTTP requests.That’s why many of us first get confused , when we hear about WCF Sessions.If we think in a more general term we can define session as something which correlates several messages together into a single conversation.
ASP.NET Sessions are:
  1. Always server-initiated.
  2. Implicitly unordered.
  3. Provide a way to preserve data across multiple requests.There is unique session id generated at the server and passed back and forth between client and server via URL or cookies.
WCF Sessions are:
  1. Initiated and terminated by the calling application.
  2. Ordered message delivery
  3. Sessions correlate a group of messages into a conversation.This correlation depdending upon the binding can be taken care at message or transport level.
  4. No data storage is involved with WCF Session
To enable session for a WCF Service first thing that needs to be done is to set the SessionMode property of the ServiceContract attribute.
The three possible values of SessionMode are:


1)NotAllowed – Session is prohibited.
2)Required – Session is mandatory.
3)Allowed – Session can be allowed.

Just setting this property is not sufficient to make the service, session enabled.The bindings of the exposed endpoints needs to support session.I developed a sample application to test out the features of WCF Session.I exposed an endpoint with BasicHttpBinding and set SessionMode to Allowed.The application was not throwing up any error but the session was not working.As I changed the SessionMode to Required I got the following error:
{“Contract requires Session, but Binding ‘BasicHttpBinding’ doesn’t support it or isn’t configured properly to support it.”}
This is because BasicHttpBinding does not support sessions.For this we have to choose wsHttpBinding or NetTcpBinding.WSHttpBinding binding, which contains support for both security sessions and reliable sessions.By default it uses only a secure session.The System.ServiceModel.NetTcpBinding binding supports transport level TCP/IP-based sessions.If we specifically turn off the message/transport level security of wsHttpBinding/NetTcpBinding and try to implement session the following exception message will appear:
Contract requires Session, but Binding ‘WSHttpBinding’ doesn’t support it or isn’t configured properly to support it. 
As per default behaviour WCF uses the same service object to cater to all the requests within a session.This can be achieved through the attribute [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)] We can mark some of the operations as Initiating/Terminating the session by using IsInitiating and IsTerminating properties of the OperationContract attribute.If IsInitiating/IsTerminating property is set then the SessionMode of the service has to Required.otherwise following exception message will appear:
{“The contract ‘ISessionDemo’ is not self-consistent — it has one or more IsTerminating or non-IsInitiating operations, but it does not have the SessionMode property set to SessionMode.Required.  The IsInitiating and IsTerminating attributes can only be used in the context of a session.”}
Initiating operations are those that must be called first. Non-initiating operations has to be be called only after at least one initiating operation has been called. Otherwise following exception message will come:
{“The operation ‘StartSession’ cannot be the first operation to be called because IsInitiating is false.”}
If I mark more than one method as Initiating then no exception will occur all of them will execute as part of the same session.Terminating operations, must be called as the last message in an existing session.After calling the Terminating method the service object and its context after the session with which the service was associated is closed. So if we try to invoke anyother method after the Terminating method the following exception message will come:
{“An operation marked as IsTerminating has already been invoked on this channel, causing the channel’s connection to terminate.  No more operations may be invoked on this channel.  Please re-create the channel to continue communication.”}
So we need to re-initilaize the proxy and related channel to invoke any other operations.
Another thing worth noting is how sessions behave when there is an exception.If there is an exception then the server channel will be faulted and the session will be destroyed along with that.This exception will be propagated to the client channel which in turn becomes faulted and proxy object instance cannot be used any further.If we try to invoke a method using this faulted channel following exception will occur
{“The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state.”}
So as a good practice we should check the state of the proxy prior to making a method call.e.g.
if (proxy!=null && proxy.State == CommunicationState.Faulted)
{
   //call the method
}
else
{
 //Recreate the proxy
}
But if the exception is handled at the service end and FaultException is generated, then the channel is not faulted and session works fine for subsequent method calls.
These are things I have noted till now working with WCF sessions.I need to check how exactly message correlation is done by checking the traces.I will discuss that in my next post


Name (Config Name) [Schemas]
Transport
Message Encoding
Message Version
Interop
Security
Session
Tx Flow
Duplex
BasicHttpBinding
(basicHttpBinding)
[http, https]
HTTP/HTTPS
Text
SOAP 1.1
Basic Profile 1.1
None, Transport, Message, Mixed
N
A binding that is suitable for communicating with WS-Basic Profile conformant Web services like ASP.NET Web services (ASMX)-based services.
WSHttpBinding
(wsHttpBinding, webHttpBinding)
[http, https]
HTTP/HTTPS
Text, MTOM
SOAP 1.2, WS-A 1.0
WS
None, Transport, Message, Mixed
None, Transport, Reliable Session
No, Yes (WS-AT)
N
A secure and interoperable binding that is suitable for non-duplex service contracts.
WS2007HttpBinding
(ws2007HttpBinding)
[http, https]
HTTP/HTTPS
Text, MTOM
SOAP 1.2, WS-A 1.0
WS-Security, WS-Trust, WS-SC, WS-SP
None, Transport, Message, Mixed
None, Transport, Reliable Session
No, Yes (WS-AT)
N
WSDualHttpBinding
(wsDualHttpBinding)
[http]
HTTP
Text, MTOM
SOAP 1.2, WS-A 1.0
WS
None, Message
Reliable Session
No, Yes (WS-AT)
Y
A secure and interoperable binding that is suitable for duplex service contracts or communication through SOAP intermediaries.
WSFederationHttpBinding
(wsFederationHttpBinding)
[http, https]
HTTP/HTTPS
Text, MTOM
SOAP 1.2, WS-A 1.0
WS-Federation
None, Message, Mixed
None, Reliable Session
No, Yes (WS-AT)
N
A secure and interoperable binding that supports the WS-Federation protocol, enabling organizations that are in a federation to efficiently authenticate and authorize users.
WS2007FederationHttpBinding
(ws2007FederationHttpBinding)
[http, https]
HTTP/HTTPS
Text, MTOM
SOAP 1.2, WS-A 1.0
WS-Federation
None, Message, Mixed
None, Reliable Session
No, Yes (WS-AT)
N
A secure and interoperable binding that derives from WS2007HttpBinding and supports federated security.
NetTcpBinding
(netTcpBinding)
[net.tcp]
TCP
Binary
SOAP 1.2
.Net
None, Transport, Message, Mixed
Transport, Reliable Session
No, Yes (OleTx)
Y
A secure and optimized binding suitable for cross-machine communication between WCF applications.
NetPeerTcpBinding
(netPeerTcpBinding)
[net.p2p]
P2P
Binary
SOAP 1.2
Peer
None, Transport, Message, Mixed
Y
A binding that enables secure, multi-machine communication.
NetNamedPipeBinding
(netNamedPipeBinding)
[net.pipe]
Named Pipe (IPC)
Binary
SOAP 1.2
.Net
None, Transport
None, Transport
No, Yes (OleTx)
Y
A secure, reliable, optimized binding that is suitable for on-machine communication between WCF applications.
NetMsmqBinding
(netMsmqBinding)
[net.msmq]
MSMQ
Binary
SOAP 1.2
.Net
None, Transport, Message, Both
No, Yes (OleTx)
N
A queued binding that is suitable for cross-machine communication between WCF applications.
MsmqIntegrationBinding
(msmqIntegrationBinding)
MSMQ
*
MSMQ
None, Transport
No, Yes
N
A binding that is suitable for cross-machine communication between a WCF application and existing MSMQ applications.


Notes: Items in bold are the defaults for features that have multiple values. “–“ = No Support

Abbreviations: WS-SC = WS-SecureConversation, WS-SP = WS-SecurityPolicy, WS-A = WS-Addressing, WS-AT = WS-AtomicTransaction, OleTx = OleTransactions
Feature
Description
Name
The name of the binding.
Config Name
The name of the binding used in configuration such as app.config or web.config.
Scheme
The supported Uri schemes.
Transport
The supported types of message transport (similar to Providers in Remoting).
Message Encoding
The supported types of message encoding.
Message Version
The supported message versions.
Interop
Names the protocol or technology with which the binding ensures interoperation.
Security
Specifies how the channel is secured:
  1. None: The SOAP message is not secured and the client is not authenticated.
  2. Transport: Security requirements are satisfied at the transport layer.
  3. Message: Security requirements are satisfied at the message layer.
  4. Mixed: This security mode is known as TransportWithMessageCredentials. It handles credentials at the message level, and integrity and confidentiality requirements are satisfied by the transport layer.
  5. Both: Both message level and transport level security are used. This ability is unique to the NetMsmqBinding.
Session
Specifies whether this binding supports session contracts.
Transaction Flow
Specifies whether transactions are enabled and the transaction flow type in ().
Duplex
Specifies whether duplex contracts are supported. Note this feature requires support for Sessions in the binding.


Thanks,

No comments: