HttpCookieSession

The HttpCookieSession sample demonstrates how to build a custom protocol channel to use HTTP cookies for session management. This channel enables communication between Windows Communication Foundation (WCF) services and ASMX clients or between WCF clients and ASMX services.

When a client calls a Web method in an ASMX Web service that is session-based, the ASP.NET engine does the following:

  • Generates a unique ID (session ID).

  • Generates the session object and associates it with the unique ID.

  • Adds the unique ID to a Set-Cookie HTTP response header and sends it to the client.

  • Identifies the client on subsequent calls based on the session ID it sends to it.

The client includes this session ID in its subsequent requests to the server. The server uses the session ID from the client to load the appropriate session object for the current HTTP context.

HttpCookieSession Channel Message Exchange Pattern

This sample enables sessions for ASMX-like scenarios. At the bottom of our channel stack, we have the HTTP transport that supports IRequestChannel and IReplyChannel. It is the job of the channel to provide sessions to the higher levels of the channel stack. The sample implements two channels, (IRequestSessionChannel and IReplySessionChannel) that support sessions.

Service Channel

The sample provides a service channel in the HttpCookieReplySessionChannelListener class. This class implements the IChannelListener interface and converts the IReplyChannel channel from lower in the channel stack to a IReplySessionChannel. This process can be divided into the following parts:

  • When the channel listener is opened, it accepts an inner channel from its inner listener. Because the inner listener is a datagram listener and the lifetime of an accepted channel is decoupled from the lifetime of the listener, we can close the inner listener and only hold on to the inner channel

                this.innerChannelListener.Open(timeoutHelper.RemainingTime());
    this.innerChannel = this.innerChannelListener.AcceptChannel(timeoutHelper.RemainingTime());
    this.innerChannel.Open(timeoutHelper.RemainingTime());
    this.innerChannelListener.Close(timeoutHelper.RemainingTime());
    
  • When the open process completes, we set up a message loop to receive messages from the inner channel.

    IAsyncResult result = BeginInnerReceiveRequest();
    if (result != null && result.CompletedSynchronously)
    {
       // do not block the user thread
       this.completeReceiveCallback ??= new WaitCallback(CompleteReceiveCallback);
       ThreadPool.QueueUserWorkItem(this.completeReceiveCallback, result);
    }
    
  • When a message arrives, the service channel examines the session identifier and de-multiplexes to the appropriate session channel. The channel listener maintains a dictionary that maps the session identifiers to the session channel instances.

    Dictionary<string, IReplySessionChannel> channelMapping;
    

The HttpCookieReplySessionChannel class implements IReplySessionChannel. Higher levels of the channel stack call the ReceiveRequest method to read requests for this session. Each session channel has a private message queue that is populated by the service channel.

InputQueue<RequestContext> requestQueue;

In the case when someone calls the ReceiveRequest method and there are no messages in the message queue, the channel waits for a specified amount of time before shutting itself down. This cleans up the session channels created for non-WCF clients.

We use the channelMapping to track the ReplySessionChannels, and we do not close our underlying innerChannel until all the accepted channels have been closed. This way HttpCookieReplySessionChannel can exist beyond the lifetime of HttpCookieReplySessionChannelListener. We also do not have to worry about the listener getting garbage collected underneath us because the accepted channels keep a reference to their listener through the OnClosed callback.

Client channel

The corresponding client channel is in the HttpCookieSessionChannelFactory class. During channel creation, the channel factory wraps the inner request channel with an HttpCookieRequestSessionChannel. The HttpCookieRequestSessionChannel class forwards the calls to the underlying request channel. When the client closes the proxy, HttpCookieRequestSessionChannel sends a message to the service that indicates that the channel is being closed. Thus, the service channel stack can gracefully shutdown the session channel that is in use.

Binding and Binding Element

After creating the service and client channels, the next step is to integrate them into the WCF runtime. Channels are exposed to WCF through bindings and binding elements. A binding consists of one or many binding elements. WCF offers several system-defined bindings; for example, BasicHttpBinding or WSHttpBinding. The HttpCookieSessionBindingElement class contains the implementation for the binding element. It overrides the channel listener and channel factory creation methods to do the necessary channel listener or channel factory instantiations.

The sample uses policy assertions for the service description. This allows the sample to publish its channel requirements to other clients that can consume the service. For example, this binding element publishes policy assertions to let potential clients know that it supports sessions. Because the sample enables the ExchangeTerminateMessage property in the binding element configuration, it adds the necessary assertions to show that the service supports an extra message exchange action to terminate the session conversation. Clients can then use this action. The following WSDL code shows the policy assertions created from the HttpCookieSessionBindingElement.

<wsp:Policy wsu:Id="HttpCookieSessionBinding_IWcfCookieSessionService_policy" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsp:ExactlyOne>
<wsp:All>
<wspe:Utf816FFFECharacterEncoding xmlns:wspe="http://schemas.xmlsoap.org/ws/2004/09/policy/encoding"/>
<mhsc:httpSessionCookie xmlns:mhsc="http://samples.microsoft.com/wcf/mhsc/policy"/>
</wsp:All>
</wsp:ExactlyOne>
</wsp:Policy>

The HttpCookieSessionBinding class is a system-provided binding that uses the binding element described previously.

Adding the Channel to the Configuration System

The sample provides two classes that expose the sample channel through configuration. The first is a BindingElementExtensionElement for the HttpCookieSessionBindingElement. The bulk of the implementation is delegated to the HttpCookieSessionBindingConfigurationElement, which derives from StandardBindingElement. The HttpCookieSessionBindingConfigurationElement has properties that correspond to the properties on HttpCookieSessionBindingElement.

Binding Element Extension Section

The section HttpCookieSessionBindingElementSection is a BindingElementExtensionElement that exposes HttpCookieSessionBindingElement to the configuration system. With a few overrides the configuration section name, the type of the binding element and how to create the binding element are defined. We can then register the extension section in a configuration file as follows:

<configuration>
    <system.serviceModel>
      <extensions>
        <bindingElementExtensions>
          <add name="httpCookieSession"
               type=
"Microsoft.ServiceModel.Samples.HttpCookieSessionBindingElementElement,
                    HttpCookieSessionExtension, Version=1.0.0.0,
                    Culture=neutral, PublicKeyToken=null"/>
        </bindingElementExtensions >
      </extensions>

      <bindings>
      <customBinding>
        <binding name="allowCookiesBinding">
          <textMessageEncoding messageVersion="Soap11WSAddressing10" />
          <httpCookieSession sessionTimeout="10" exchangeTerminateMessage="true" />
          <httpTransport allowCookies="true" />
        </binding>
      </customBinding>
      </bindings>
    </system.serviceModel>
</configuration>

Test Code

Test code for using this sample transport is available in the Client and Service directories. It consists of two tests—one test uses a binding with allowCookies set to true on the client. The second test enables explicit shutdown (using the terminate message exchange) on the binding.

When you run the sample, you should see the following output:

Simple binding:
AddItem(10000,2): ItemCount=2
AddItem(10550,5): ItemCount=7
RemoveItem(10550,2): ItemCount=5
Items
10000, 2
10550, 3
Smart binding:
AddItem(10000,2): ItemCount=2
AddItem(10550,5): ItemCount=7
RemoveItem(10550,2): ItemCount=5
Items
10000, 2
10550, 3

Press <ENTER> to terminate client.

To set up, build, and run the sample

  1. Install ASP.NET 4.0 using the following command.

    %windir%\Microsoft.NET\Framework\v4.0.XXXXX\aspnet_regiis.exe /i /enable
    
  2. Ensure that you have performed the One-Time Setup Procedure for the Windows Communication Foundation Samples.

  3. To build the solution, follow the instructions in Building the Windows Communication Foundation Samples.

  4. To run the sample in a single- or cross-machine configuration, follow the instructions in Running the Windows Communication Foundation Samples.