Introduction
This article considers a possible solution for migrating the session
management of an existing ASP application into ASP.NET using a simple web
service. This solution might be pragmatic for an existing ASP web farm that is
currently enforcing server-affinity due to the in-memory session usage and
provides a strategy for iteratively upgrading to ASP.NET.
Background
(Click
Here if you know all this
and just want to skip to the code)
(Click Here if you just want to know how damn slowly
this would run)
Existing ASP applications often make use of �ASP sessions�, a feature built
into classic ASP that allows data to be temporarily stored within the memory of
the web server. The well-documented limitation is that ASP session state is
machine specific. For larger solutions a number of web servers are likely to be
used and requests directed to any of the web servers in the farm, therefore any
in-memory session state will not automatically follow subsequent requests. Each
ASP server provides its own session state and unless the user is lucky and
happens to return to the same server, the session state is lost.
A solution to this server-affinity caused by in-memory ASP sessions has been
to use server management products (such as BigIP) to force users back to the
same server within a farm. One way in which this is achieved is by using a
cookie on the client workstation, which the server manager uses to direct the
user back to the same server on each request. This can limit scalability, incurs
a higher level of maintenance and has a higher risk of errors due to server
failures (e.g. the session is lost if the server fails).
ASP.NET finally addressed these problems and allowed us to store the session
information off of the individual web servers and on a central database or state
server. Wonderful, problem solved. Errrm, but what about that very large
investment in the existing ASP code-base? Throw it away and start again in .NET?
Rather than try to sell a big-bang approach of re-writing everything in
ASP.NET to your stakeholders, an alternative solution is to come up with an
iterative method of migrating code to the new model as part of your normal
lifecycle. It would therefore be very helpful if the old ASP code and the new
ASP.NET code could share a common session state during this process. You manage
your risk and hopefully keep your job.
There are a few solutions that come to mind and might serve as a strategic
solution to this problem of server-affinity when using classic ASP sessions,
which include:
- Custom component or ASP/ADO script to read/write directly to a custom
session database implementation
- Custom component to access ASP.NET session database directly
- ASP to ASP.NET session-bridge using web services and ASP.NET built in
sessions
For this article, we will be concentrating on the last of these options,
although the disadvantages (such as performance) and advantages of each will be
touched upon. We will also include some basic performance figures that compare
the web service, ASP/ADO to custom database and in-memory ASP session solutions,
so you can get a feel for what is being traded off for your chosen solution.
ASP to ASP.NET Bridge / Web-Service Solution
This solution avoids the need for a database session implementation and does
not even assume the state is stored in a database (so you could use a state
server, or even a in-memory sessions). We just implement a simple bridge from
ASP to ASP.NET via a web service. If you do want to use a central database (most
likely scenario), then this is a simple part of the ASP.NET application
configuration (web.config and ASPState database).
The code for the methods used to set and get session data will reside in a
JavaScript file that must be stored locally to the ASP applications. The
JavaScript functions use the MSXML HTTP functions to perform server-side calls
to the ASP.NET web service and are responsible for the cookies being passed back
to the user workstation in order for this whole mechanism to work.
Advantages:
- Supports web-farm deployment without server affinity for higher scalability
- Simple implementation making strategic use of common ASP.NET session state
- Loosely coupled to session management (connectionless HTTP interface, port
80, can be firewalled etc)
- Uses well-tested ASP.NET session implementation
Disadvantages:
- Much slower interface than in-memory ASP Sessions and custom database
implementations
Intrinsic ASP Mechanism
The session state is in the web-server memory and in the shape of a
dictionary or hash table (e.g. key-value pairs), which can be set and read for
the duration of a user session. ASP maintains session state by providing the
client with a unique key assigned to the user when the session begins. This key
is stored in an HTTP cookie that the client sends to the server on each request.
The server then uses the key from the cookie and knows which session state to
use for any particular request.
The mechanism built into ASP for maintaining session state has some clear
advantages, namely speed and ease of use. All the session data is stored on the
same server as the ASP that is being executed, thus it is very fast from not
having to suffer any network hops. However, as mentioned earlier, storing the
data on the same server also has a major disadvantage of forcing a user to
return to the same server in order to retrieve session data. This introduces the
need for server affinity within a web-farm environment, which lessens the
advantages of a low-cost scale-out strategy - you want to bung in cheap servers
to increase scaleability as needed.
Advantages:
- Fast in-memory access to session information
- Uses standard ASP code base
- Uses well-tested ASP session implementation
Disadvantages:
- Limited ability to scale-out in web farms (server affinity)
- Server failures are visible to users due to lost sessions
- Utilizes memory on the web-server (in terms of small footprint good, large
footprint bad)
ASP/ADO to Database Solution
Rather than storing session data on the same server, this method establishes
a database connection between the ASP application server and a database server.
This allows data to be stored on a central database or database cluster,
separate from the servers on which the ASP applications are executed. No code is
included for this, given my solution is integrated into a larger proprietry
session management solution.
The code to set and get session data could also reside in a JavaScript file
that supports the same methods as the web-service based solution, allowing the
ASP to use either option by replacing the JavaScript include file. As well as
using a custom database schema, directly accessing the ASP.NET session database
(e.g. ASPState) could also be considered.
Advantages:
- Supports web-farm deployment without server affinity
- Faster interface than ASP.NET web service
Disadvantages:
- Maintain code for a custom session implementation
- Slower interface than in-memory ASP Sessions
- Requires database connections from web servers to database
Performance
<Disclaimer> The results published here are of some simple
performance tests that were run on a couple of fairly decent Windows 2000 web
servers, along a SQL Server 2000 database cluster. Obviously if you run the
online demo on fullerdata.com, you will get different results - please don't
abuse this online demo, otherwise I might have to take it down due to my ISP
bandwidth limit (and the need to feed my family! :-). So basically, run your own
serious performance tests in your own environment. </Disclaimer>
Using one of the solutions discussed, specifically the ASP/ADO to database
solution or the ASP/ASP.NET web service solution, does present additional
performance considerations. In-memory ASP Sessions are extremely fast in getting
and setting key-value pairs due to the fact that the session data is stored
locally on each web server. Any solution that stores the data on a separate
server/database presents an extra cost, mainly in connecting to the database and
then transferring information across the more complex interface. The ASP.NET /
web-service solution presents a further cost of invoking the web service over
HTTP, which in turn uses the ASP.NET database sessions.
The two solutions presented cannot come close to matching the single-user
speed of in-memory ASP Sessions. However, whilst in-memory sessions are fast for
a small number of users, web-server memory is being used per user, and thus for
higher volume the performance degrades. It's like comparing a runner doing a
sprint versus a marathon, in that you can make an application that responds very
quickly for low number of users by placing a lot of information in each users
session. However, once the number of users increases and the available memory
becomes a contention for the web-server, then you basically look good off of the
start line and then die of a heart attack. :-)
In order to minimize the round-trip cost of accessing sessions, methods for
getting and setting multiple key-value pairs with one call have been made
available. This minimizes the number of network connections to the database or
HTTP requests to the web service. This is particularly important for the
web-service solution, given the cost of getting/setting will be high due to the
expense of an HTTP request.
The data in the table below provides a high level indication of the
performance that can be expected by each of the solutions. It measures the time
to last byte (TTLB) for getting or setting five key-value pairs. The Microsoft
Web Application Stress Tool was used to perform the tests with a stress level of
25 threads for a one-minute duration.
Method |
Get Data (ms) |
Set Data (ms) |
|
5 values |
1 value |
5 values |
1 value |
In-memory ASP Sessions |
46 |
9 |
34 |
7 |
ASP/ASP.NET web service individually |
4321 |
864 |
3397 |
679 |
ASP/ASP.NET web service grouped |
711 |
142 |
990 |
198 |
ASP/ADO database individually |
346 |
69 |
841 |
168 |
ASP/ADO database grouped |
163 |
33 |
860 |
172 |
As can be seen, the in-memory ASP sessions performance is faster than both
the ADO and ASP.NET / web service database session solutions. This can be offset
significantly by accessing the session using the grouped methods given the
higher cost of the round-trips. Whilst the web-service method is slower than the
direct-database solution, it not as significant as one might expect (for a
grouped get, twice as slow).
Using the code
Intrinsic ASP
The use of built-in ASP sessions that are likely being used within your ASP
applications will look something like the following:
Session("Sky") = "Blue";
On subsequent pages these values are read and the application has access to
these values:
var skyString = Session("Sky");
New API Syntax
The following syntax would need to be used instead of the normal ASP session
syntax, so that the JavaScript functions that encapulate the session bridge is
used.
Setting One Key-Value Pair
In setting one key-value pair of session data, the syntax would be very
similar to the current syntax of the built-in ASP sessions above. The following
code would replace the current code that utilises intrinsic ASP sessions.
To set session data:
SetSessionValue("Sky", "Blue");
To get session data:
var SkyString = GetSessionValue("Sky");
This method would be recommended when relatively few key-value pairs (three
or less) are set or retrieved on a single ASP.
Setting Multiple Key-Value Pairs
The cost of the above method is that for each setting or retrieving of a
key-value pair, a round-trip to the web service is made. Given this can be a
relatively costly operation in terms of response time, the following method is
recommended on an ASP where a significant number (more than 3) key-value pairs
are set or retrieved.
To set session data:
var sessionInfo = NewSession();
sessionInfo.Add(�Sky�, "Blue");
sessionInfo.Add(�Grass�, �Green�);
.
.
.
SetSession(sessionInfo);
To get session data:
var sessionInfo = GetSession();
var skyString = sessionInfo.Item(�Sky�);
var grassString = sessionInfo.Item(�Grass�);
.
.
.
Java Script Example
<%@ Language="JScript" %>
<script language="JScript" runat="server" src="ASPSessionWS.js" />
<%
var sessionInfo = NewSession();
sessionInfo.Item("Sky") = "Blue";
sessionInfo.Item("Grass") = "Green";
SetSession(sessionInfo);
var retrievedSession = GetSession();
var sSky = retrievedSession.Item("Sky");
var sGrass = retrievedSession.Item("Grass");
Response.Write(sSky + "<br>");
Response.Write(sGrass + "<br>");
%>
Visual BASIC Example
<%@ Language="VBSCRIPT" %>
<script language="JScript" runat="server" src="ASPSession.js" />
<%
Dim sessionInfo
Set sessionInfo = NewSession()
sessionInfo.Item("Sky") = "Blue"
sessionInfo.Item("Grass") = "Green"
SetSession(sessionInfo)
Dim retrievedSession
Set retrievedSession = GetSession()
Dim sSky
sSky = retrievedSession.Item("Sky")
Dim sGrass
sGrass = retrievedSession.Item("Grass")
Response.Write(sSky & "<br>")
Response.Write(sGrass & "<br>")
%>
Web Service Implementation
The web service consists of four simple methods, which supports getting /
setting individual values within the ASP.NET session, and the slightly (but not
much) more complex methods of getting / setting groups of session variables by
the use of an XML payload.
public string getSessionValue(string sessionVariable)
public bool setSessionValue(string sessionVariable, string sessionValue)
public string getSessionValues()
public bool setSessionValues(string xmlSessionValues)
For the ASP.NET web service to support the creation and maintenance of
sessions, then the following attribute is included in each of the methods. This
then returns the ASP.NET_SessionId cookie into the responses, which can be used
to bridge the session to the ASP code.
[WebMethod(EnableSession=true)]
That's pretty much it. You might want to include some more sophisticated
implementation to integrate other server-side state information into the same
interfaces. This version of the web service also has no authentication or other
restriction on use, which given the open nature of a web service, might not be
appropriate for your environment - therefore you might want to use a GUID or
some other identification to show that a user is logged on as a parameter in
each of the web service interfaces. Nice and simple.
ASP Bridge Implementation
The ASP side of the bridge is basically depdendent on the
MSXML2.ServerXMLHTTP COM interface to perform calls to the web service
server-side and the Scripting.Dictionary to provide a hash-table for maintaining
a temporary (lifetime of page) copy of the session. Before the call is made to
the web-service, it is important to ensure that if there is already an
ASP.NET_SessionId cookie on the client workstation, it is passed as part of the
server-side web service request. Likewise, that any cookies received from the
web service are then included in the ASP response so that they are written to
the client workstation for subsequent requests.
var xmlHTTP = Server.CreateObject("MSXML2.ServerXMLHTTP");
xmlHTTP.open("POST", sURL, false);
var clientCookie = "" + Request.Cookies("ASP.NET_SessionId");
xmlHTTP.setRequestHeader("cookie", "ASP.NET_SessionId=" +
clientCookie + "; path=/;");
.
.
.
Response.Cookies("ASP.NET_SessionId") = httpCookie;
The dictionary object is used to convert to and from a simple XML payload
(basically just a collection of <SessionItem> nodes).
var dctSession = new ActiveXObject("Scripting.Dictionary");
var re = new RegExp("<SessionItem ", "g");
So, perhaps not the most elegant implementation, but it makes good use of
what is available in ASP without resorting to rolling our own component. Again,
nice and simple.
Deployment
A JavaScript file �ASPSessionWS.js� must be copied locally to where the ASP
application is run. Include this script tag in any ASP file that utilises the
ASP.NET sessions:
<script language="�Jscript�" runat="�server�" src=�\Script\ASPSessionWS.js� >
</script>
The web server only needs port 80 access available and the URL of where you
have copied the web service updated in this script file (see below). The web
service deployment is a simple matter of just copying your .asmx file to the web
server that will be maintaining the session state, which in turn will need to
change the web.config file to reflect wherever your database has been deployed
(MSDN has tutorials for setting this up here).
function GetWebService(Function, Parameters)
{
var xmlPayload = "";
var sURL = "http://www.fullerdata.com/ASPBridge/bridge.asmx" + "/" +
Function;
Summary
Using a web service as a bridge from legacy ASP sessions to new ASP.NET
sessions provides a pragmatic means of migrating your applications to new
technologies, allowing the old and the new to share a single session context.
The performance cost is significant, but might be a "good enough" solution as an
interim for migration completely to .NET, especially within web-farm
environments where the removal of web server affinity is a priority.
Links
There are already a number of other articles about using ASP.NET session
state on CodeProject, of which the following are notable (IMHO):
There are some excellent articles on MSDN about ASP.NET sessions which are
definitely worth reading:
History
- March 2004 - Code Deployed to FullerData.com and Article Submitted to
CodeProject.com