March 28, 2011, 3:52 p.m.
posted by angryuser
SessionsThe concept of sessions is difficult to define. Some people use the word to define any conversation among a set of messaging endpoints. But I prefer a more liberal definition: A session is the logical and unique name given to a set of semantically and temporally related messages. It's a way to let a message receiver state that a particular message is part of the same conversation, or session. Examples of a session might be the series of messages
HTTP SessionsMost typically, sessions are seen in Web applications in which there is a need to tie a particular browser instance together. This is usually done in one of two ways: either with an HTTP cookie; or with a special URL value, either in the URL or as a POST value that is carried forward from each Web page. In both instances, the value is a GUID (Global Unique Identifier) that eventually times out. With Active Server Pages (ASPs), and ASP.NET, there is a Session object that is available only on the server side and which can place custom application data. The correct Session object is then presented to the server code each time the HTTP GET or POST request contains the session cookie, as based on the value. This also can work well for Web services, and as a result, ASP.NET Web services support the exact same programming mode, using the same cookie-based Session key. To take advantage of this server-side feature, the client that is sending the request message must obey the common rules for HTTP cookie handling. To enable this with the .NET Web services proxy class, you need to create a CookieContainer object and set the CookieContainer property of the class to this instance: MyClientProxyClass clientProxy = new MyClientProxyClass(); clientProxy.CookieContainer = new CookieContainer(); Now, whenever you make a method call to a server, the correct cookies, including HTTP-based session cookies, will be sent to this client proxy class and accepted by it. Because this container is programmatically accessible, you can also persist it to disk or to a database. In fact, you will have to manage this collection of cookies in between client-proxy-class lifetimes, because the underlying .NET runtime won't do this for you the same way the browser does. On the server side, the session is initiated by code that tries to access it. This requires no explicit call to create a cookie. Instead, you need only to access a session value. The ASP.NET infrastructure will automatically do the right thing in terms of looking for a cookie, and creating one if none exists yet. Listing 14.1 shows exactly how easy session initiation can be with ASP.NET Web services. Similar levels of ease exist in most HTTP-aware SOAP stacks. Sessions with ASP.NET Web Services
<%@ WebService Class="SessionTest" Language="C#" %>
using System.Web.Services;
using System.Web;
public class SessionTest : WebService
{
[WebMethod]
public String AddToSession( String text )
{
String s = (String)Session["text"];
S = s + text;
Session["text"] = s;
return s;
}
}
Message-Level SessionsAs of this writing, there is no SOAP-based specification for sessions that has gained wide acceptance in the industry. But there has been enough discussion to make some good guesses about how a session specification would work. The first thing to decide is whether session information should be header based, body based, or some combination. It seems logical that sessions are exactly the kind of out-of-band information that is best kept in the header. We'll keep that as a working hypothesis.
With that in mind, you can imagine a very simple session header, called <Session>, with a single child element, called <Id>. We'll need a namespace as well, so we'll use http://keithba.com/2002/05/Session. Listing 14.2 shows the resulting session header.
An Example Session Header
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<Session xmlns="http://keithba.com/2002/05/Session">
<Id>uuid:1111-1111-1111-1111</Id>
</Session>
</soap:Header>
<soap:Body>
...
</soap:Body>
</soap:Envelope>
It's interesting that the <Id> can take as a value any URI. In the schema for this, it would be of type <xsd:anyURI>. This allows the session's identifier to be a GUID, as in Listing 14.2, or even a more "normal" URI such as http://keithba.com/purchaseOrder/20202198. It would be unusual not to have a unique session identifier; therefore, anything but a GUID would be uncommon. With ASP.NET's HTTP cookies, there is no specific way to initiate a session, and the session is truly understood only on the server's end. In effect, there is no client side of a session. With a SOAP-based session protocol, on the other hand, it makes sense to want some additional features:
With this in mind, we need to add an <Initiate> element—which also contains the expiration date and time of the session—as well as a <Terminate> element for ending the session. Listings 14.3 and 14.4 show a session initiation and termination, respectively. An Example of Session Initiation
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<Session xmlns="http://keithba.com/2002/05/Session">
<Initiate>
<Expires>4/4/2002 12:00:00PM PST</Expires>
</Initiate>
<Id>uuid:1111-1111-1111-1111</Id>
</Session>
</soap:Header>
<soap:Body>
...
</soap:Body>
</soap:Envelope>
An Example of Session Termination
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Header>
<Session xmlns="http://keithba.com/2002/05/Session">
<Terminate />
<Id>uuid:1111-1111-1111-1111</Id>
</Session>
</soap:Header>
<soap:Body>
...
</soap:Body>
</soap:Envelope>
The classes for building this session header are fairly straightforward. Listing 14.5 shows the class for the <Session> header itself. The <Session> Header Class
[XmlRoot(Namespace="http://keithba.com/2002/05/Session")]
public class Session : SoapHeader
{
private String _Id;
public String Id
{
get
{
return _Id;
}
set
{
_Id = value;
}
}
private Initiate _Initiate;
public Initiate Initiate
{
get
{
return _Initiate;
}
set
{
_Initiate = value;
}
}
private Terminate _Terminate;
public Terminate Terminate
{
get
{
return _Terminate;
}
set
{
_Terminate = value;
}
}
}
Notice a couple of things from Listing 14.5:
The Initiate class contains an expiration date and time as well, as shown in Listing 14.6. The Initiate Class
public class Initiate
{
private DateTime _Expires;
public DateTime Expires
{
get
{
return _Expires;
}
set
{
_Expires = value;
}
}
}
The Terminate class is entirely uninteresting, but it must be a class in order to be a child element. Here is the code:
public class Terminate
{
}
Listing 14.7 shows the schema for the session header. The Session Header Schema
<xs:schema
targetNamespace="http://keithba.com/2002/05/Session"
xmlns:tns="http://keithba.com/2002/05/Session"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
attributeFormDefault="unqualified">
<xs:element name="Session" type="tns:Session"/>
<xs:complexType name="Session">
<xs:sequence>
<xs:element
name="Id"
type="xs:string"
minOccurs="0"/>
<xs:element
name="Initiate"
type="tns:Initiate"
minOccurs="0"/>
<xs:element
name="Terminate"
type="tns:Terminate"
minOccurs="0"/>
<xs:any namespace="##other"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Initiate">
<xs:sequence>
<xs:element name="Expires" type="xs:dateTime"/>
<xs:any namespace="##other"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Terminate">
<xs:sequence>
<xs:any namespace="##other"/>
</xs:sequence>
</xs:complexType>
</xs:schema>
Notice that all of the elements have an <any> element defined for them. This allows for extending this schema without needing to version it— which will come in handy later, when we extend this session to handle reliability features. Figure diagrams this schema. 1. A Graphical Look at the Session Schema
At this point, using this session class within a service would be very simple:
public class Service1 : WebService
{
public Session Session;
[WebMethod]
[SoapHeader("Session",
Direction=SoapHeaderDirection.InOut)]
public void SubmitPO( String PO )
{
//code would go here
}
}
The client side would work similarly to any header. Of course, having access to these headers isn't enough, because the application needs to be able to tie any particular sessions together. However, it's outside the scope of this book to fully explain how this could happen. In general, a property bag or named object dictionary would be more than enough. The only code then needed would be another dictionary to contain the specific session's dictionary. |
- Comment
