Skip to content

Instantly share code, notes, and snippets.

@nzbart
Last active December 31, 2015 16:39
Embed
What would you like to do?
Allow multiple web applications under IIS to use the same SQL server session state database without modifying the database stored procedures.
/*
* This code is a workaround for the fact that session state in ASP.NET SqlServer mode is isolated by application. The AppDomain's appId is used to
* segregate applications, and there is no official way exposed to modify this behaviour.
*
* Some workarounds tackle the problem by modifying the ASP.NET state database. This workaround approaches the problem from the application code
* and will be appropriate for those who do not want to alter the database. We are using it during a migration process from old to new technology stacks,
* where we want the transition between the two sites to be seamless.
*
* As always, when relying on implementation details, the reflection based approach used here may break in future / past versions of the .NET framework.
* Test thoroughly.
*
* Usage: add this to your Global.asax:
* protected void Application_BeginRequest()
* {
* SessionStateCrossApplicationHacker.SetSessionStateApplicationName("an application");
* }
*/
using System;
using System.Data.SqlClient;
using System.Globalization;
using System.Reflection;
using System.Web.SessionState;
public static class SessionStateCrossApplicationHacker
{
static string _appName;
static readonly object _appNameLock = new object();
public static void SetSessionStateApplicationName(string appName)
{
if (_appName != appName)
{
lock (_appNameLock)
{
if (_appName != appName)
{
SetSessionStateApplicationNameOnceOnly(appName);
_appName = appName;
}
}
}
}
static void SetSessionStateApplicationNameOnceOnly(string appName)
{
//get the static instance of SqlSessionStateStore.SqlPartitionInfo from System.Web.SessionState.SqlSessionStateStore.s_singlePartitionInfo
var sqlSessionStateStoreType = typeof (SessionStateMode).Assembly.GetType("System.Web.SessionState.SqlSessionStateStore");
var sqlSessionStatePartitionInfoInstance = GetStaticFieldValue(sqlSessionStateStoreType, "s_singlePartitionInfo");
if (sqlSessionStatePartitionInfoInstance == null)
throw new InvalidOperationException("You'll need to call this method later in the pipeline - the session state mechanism (SessionStateModule, which is an IHttpModule) has not been initialised yet. Try calling this method in Global.asax's Application_BeginRequest. Also make sure that you do not specify a partitionResolverType in your web.config.");
//ensure that the session has not been used prior to this with an incorrect app ID
var isStaticSqlPartitionInfoInitialised = GetFieldValue<bool>(sqlSessionStatePartitionInfoInstance, "_sqlInfoInited");
if (isStaticSqlPartitionInfoInitialised)
throw new InvalidOperationException("You'll need to call this method earlier in the pipeline - before any sessions have been loaded.");
//force initialisation of the static SqlSessionStateStore.SqlPartitionInfo instance - otherwise this will happen later and overwrite our change
var connectionString = GetFieldValue<string>(sqlSessionStatePartitionInfoInstance, "_sqlConnectionString");
using (var connection = new SqlConnection(connectionString))
{
connection.Open();
CallInstanceMethod(sqlSessionStatePartitionInfoInstance, "InitSqlInfo", connection);
}
//calculate and set the application hash code
string applicationNameHashCode = GetHashCode(appName).ToString("x8", CultureInfo.InvariantCulture);
GetField(sqlSessionStatePartitionInfoInstance, "_appSuffix").SetValue(sqlSessionStatePartitionInfoInstance, applicationNameHashCode);
}
static int GetHashCode(string appName)
{
string s = appName.ToLower();
int hash = 5381;
int len = s.Length;
for (int i = 0; i < len; i++)
{
int c = Convert.ToInt32(s[i]);
hash = ((hash << 5) + hash) ^ c;
}
return hash;
}
static void CallInstanceMethod(object instance, string methodName, params object[] parameters)
{
var methodInfo = instance.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);
methodInfo.Invoke(instance, parameters);
}
static object GetStaticFieldValue(Type typeWithStaticField, string staticFieldName)
{
return typeWithStaticField.GetField(staticFieldName, BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
}
static FieldInfo GetField(object instance, string name)
{
return instance.GetType().GetField(name, BindingFlags.NonPublic | BindingFlags.Instance);
}
static T GetFieldValue<T>(object instance, string name)
{
return (T)GetField(instance, name).GetValue(instance);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment