Monday, June 1, 2009

Using SoapExtension to manage sessions

Intro

When scalability is a main issue in your application, it is common to design and build a stateless solution.
Stateless design allows our application to be duplicated to several servers as the amount of users grows, load balancing could be taken to the max, plus it saves resources "wasted" on session management.
The downside is that in most applications having session identifier or other session level parameters is really convenient (sometimes even necessary).

So if scalability is part of your design - distributed session management is probably something you are considering...
But if you don't really need a big session management solution that will cost you in performance (no matter what..) and you only want 2-3 parameters that will help you identify some user's preference without all the fuss, consider - soap extension.

Implementation

We start implementing by building a class that represents these parameters we want to pass constantly from client to server.
Surprisingly this class will inherit from SoapHeader.


[XmlRoot("Keystone", Namespace = "urn:com-sample-dpe:soapextension")]
public class SessionSoapHeader : SoapHeader
{
private string _customer;
private string _version;

public string Version
{
get { return _version; }
set { _version = value; }
}

public string Customer
{
get { return _customer; }
set { _customer = value; }
}
}


Next we'll create an attribute to make our proxy implement the use of this extension.


// Create a SoapExtensionAttribute for the SOAP Extension that can be
// applied to an XML Web service method.
[AttributeUsage(AttributeTargets.All)]
public class SessionSoapHeaderExtensionAttribute : SoapExtensionAttribute
{
public override Type ExtensionType
{
get { return typeof(SessionSoapHeaderExtension); }
}

public override int Priority
{
get
{
return 100;
}
set
{
}
}
}


And to the actual Soap extension...


public class SessionSoapHeaderExtension : SoapExtension
{
public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute)
{
return null;
}

public override object GetInitializer(Type WebServiceType)
{
return null;
}

public override void Initialize(object initializer)
{
return;
}

public override Stream ChainStream(Stream stream)
{
return stream;
}

public override void ProcessMessage(SoapMessage message)
{
switch (message.Stage)
{
case SoapMessageStage.BeforeSerialize:
//Add the CustomSoapHeader to outgoing client requests
if (message is SoapClientMessage)
{
AddHeader(message);
}
break;

case SoapMessageStage.AfterSerialize:
break;

case SoapMessageStage.BeforeDeserialize:
break;

case SoapMessageStage.AfterDeserialize:
if (message.Headers.Count > 0)
{
XmlElement headerXml = ((((System.Web.Services.Protocols.SoapUnknownHeader)
(message.Headers[0]))).Element as XmlElement);
if (headerXml != null)
{
if (headerXml.ChildNodes.Count > 0)
{
foreach (XmlNode headerItem in headerXml.ChildNodes)
{
if (headerItem.Name.ToLower().IndexOf("customer") != -1)
{
HttpContext.Current.Items.Add(headerItem.Name, headerItem.InnerText);
SessionSoapHeaderExtension.CustomerName = HttpContext.Current.Items[headerItem.Name].ToString();
}
if (headerItem.Name.ToLower().IndexOf("version") != -1)
{
HttpContext.Current.Items.Add(headerItem.Name, headerItem.InnerText);
SessionSoapHeaderExtension.Version = HttpContext.Current.Items[headerItem.Name].ToString();
}
}
}
}
}
if (message is SoapClientMessage)
{
//could be usefull
}
break;
}
}

private void AddHeader(SoapMessage message)
{
SessionSoapHeader header = new SessionSoapHeader();
header.Customer = (!string.IsNullOrEmpty(Customer) ? Customer : string.Empty);
header.Version = (!string.IsNullOrEmpty(Version) ? Version : string.Empty); ;
header.MustUnderstand = false;
message.Headers.Add(header);
}
}


Final step, configure both server & client to use this extension.

In server:


<webServices>
<soapExtensionTypes>
<add type="myNS.SessionSoapHeaderExtension, myNS"/>
</soapExtensionTypes>
</webServices>


In client you can configure similarly using strong name & registering the dll in GAC or add the attribute to the reference.cs of the required proxy.

client config:


<system.web>
<webServices>
<soapExtensionTypes>
<add type="myNS.SessionSoapHeaderExtension, myNS,Version=1.0,Culture=neutral, PublicKeyToken=f54c79bbbb6454bc" />
</soapExtensionTypes>
</webServices>
</system.web>



That's it, this simple implementation will allow you to pass user's preference or identification elegantly in header and save you the use of heavy session management solution when not needed.

Any kind of feedback or questions would be appreciated.

Till next time.
Diego