Tuesday, June 26, 2007

Wrapped InProcSessionStateStore

Although the .NET 2.0 Provider Model offers three built-in possibilities for persisting the session context information, there exists frameworks which limit the choice to the InPronSessionStateStore.

One of such frameworks is the Visual Web GUI Ajax framework designed to fill the gap between Windows.Forms and Web.Forms programming. The VWG framework looks very, very promising, however I feel that its development slows down a bit.

Nonetheless, the main idea of VWG is dumb client, allmighty server where the interface is rendered in the client's web browser and the application logic is executed completely on the server.

There are few limitations and one of them is the session state management - the session context collects much more data than the usual Web.Forms session does. Since VWG objects are not serializable, the SqlServer session state store cannot be used and what I immediately though about was: how much data is exactly stored in the session context?

To be able to inspect the session store it would be perfect to just override the InProcSessionStateStore. However, the class is internal. The idea below is to build a custom session state store which uses the InProcSessionStateStore internally but makes it possible to put custom logic into the session store-retrieve pipeline.

Two critical methods are GetItemExclusive and SetAndReleaseItemExclusive. This is where the custom code can be injected into the pipeline. The code can, for example, log the content of the session store or modify it somehow.

Can I then make my own custom serializer and store the session context in some persistent storage from the wrapped inproc store? you would ask.

Good question. I was not able to find a generic solution to the serialize non-serialized objects problem. The GeneralSerializableAdapter solution proposed here is probably a step in the right direction, however it does not serialize collections correctly (or when it does after you tweak it, it serializes far too much!) neither it is able to serialize non-serialized properties.

If you are able to write a generic serializer which would be able to serialize objects which are not marked as serialized (and do not contain parameterless constructors), please feel free to go a step further then I did and extend the inproc state store wrapper with the generic serializer.

Here is the code:

   1: /* Wiktor Zychla, 2007 */
   2: using System;
   3: using System.Data;
   4: using System.Configuration;
   5: using System.Web;
   6: using System.Web.Security;
   7: using System.Web.UI;
   8: using System.Web.UI.WebControls;
   9: using System.Web.UI.WebControls.WebParts;
  10: using System.Web.UI.HtmlControls;
  11: using System.Web.SessionState;
  12: using System.Web.Hosting;
  13: using System.Reflection;
  14: using System.Data.SqlClient;
  15: using System.Runtime.InteropServices;
  16: using System.Diagnostics;
  17: using System.IO;
  18: using System.Collections;
  19: using System.Runtime.Serialization.Formatters.Soap;
  20: using System.Xml.Serialization;
  21: using System.Runtime.Serialization.Formatters.Binary;
  22: using System.Runtime.Serialization;
  23:  
  24: public class WrappedInProcSessionStateProvider : SessionStateStoreProviderBase
  25: {
  26:     private object store;
  27:     private Type storeType;
  28:  
  29:     public override void Initialize( 
  30:         string name, 
  31:         System.Collections.Specialized.NameValueCollection config )
  32:     {
  33:         storeType = typeof( SessionStateStoreProviderBase )
  34:             .Assembly.GetType( "System.Web.SessionState.InProcSessionStateStore" );
  35:         store = Activator.CreateInstance( storeType );
  36:  
  37:         base.Initialize( name, config );
  38:     }
  39:  
  40:     public override SessionStateStoreData CreateNewStoreData( HttpContext context, int timeout )
  41:     {
  42:         if ( store != null )
  43:         {
  44:             return (SessionStateStoreData)
  45:                 storeType.InvokeMember(
  46:                 "CreateNewStoreData",
  47:                 System.Reflection.BindingFlags.InvokeMethod | 
  48:                 System.Reflection.BindingFlags.Instance | 
  49:                 System.Reflection.BindingFlags.Public,
  50:                 null, store, new object[] { context, timeout }
  51:             );
  52:         }
  53:         else
  54:             throw new Exception( "State not initialized" );
  55:     }
  56:  
  57:     public override void CreateUninitializedItem( 
  58:         HttpContext context, string id, int timeout )
  59:     {
  60:         if ( store != null )
  61:         {
  62:             storeType.InvokeMember(
  63:                 "CreateUninitializedItem",
  64:                 System.Reflection.BindingFlags.InvokeMethod | 
  65:                 System.Reflection.BindingFlags.Instance | 
  66:                 System.Reflection.BindingFlags.Public,
  67:                 null, store, new object[] { context, id, timeout }
  68:             );
  69:         }
  70:     }
  71:  
  72:     public override void Dispose()
  73:     {
  74:         if ( store != null )
  75:         {
  76:             storeType.InvokeMember(
  77:                 "Dispose",
  78:                 System.Reflection.BindingFlags.InvokeMethod | 
  79:                 System.Reflection.BindingFlags.Instance | 
  80:                 System.Reflection.BindingFlags.Public,
  81:                 null, store, null
  82:             );
  83:         }
  84:     }
  85:  
  86:     public override void EndRequest( HttpContext context )
  87:     {
  88:         if ( store != null )
  89:         {
  90:             storeType.InvokeMember(
  91:                 "EndRequest",
  92:                 System.Reflection.BindingFlags.InvokeMethod | 
  93:                 System.Reflection.BindingFlags.Instance | 
  94:                 System.Reflection.BindingFlags.Public,
  95:                 null, store, new object[] { context }
  96:             );
  97:         }
  98:     }
  99:  
 100:     public override SessionStateStoreData GetItem( 
 101:         HttpContext context, string id, out bool locked, 
 102:         out TimeSpan lockAge, out object lockId, out SessionStateActions actions )
 103:     {
 104:         if ( store != null )
 105:         {
 106:             object[] args = new object[] { context, id, null, null, null, null };
 107:  
 108:             SessionStateStoreData ret =
 109:                 (SessionStateStoreData)
 110:                 storeType.InvokeMember(
 111:                 "GetItem",
 112:                 System.Reflection.BindingFlags.InvokeMethod | 
 113:                 System.Reflection.BindingFlags.Instance | 
 114:                 System.Reflection.BindingFlags.Public,
 115:                 null, store, args
 116:             );
 117:  
 118:             locked  = (bool)args[2];
 119:             lockAge = (TimeSpan)args[3];
 120:             lockId  = args[4];
 121:             actions = (SessionStateActions)args[5];
 122:  
 123:             return ret;
 124:         }
 125:         else
 126:             throw new Exception( "State not initialized" );
 127:     }
 128:  
 129:     public override SessionStateStoreData GetItemExclusive( 
 130:         HttpContext context, string id, out bool locked, 
 131:         out TimeSpan lockAge, out object lockId, out SessionStateActions actions )
 132:     {
 133:         if ( store != null )
 134:         {
 135:             object[] args = new object[] { context, id, null, null, null, null };
 136:  
 137:             SessionStateStoreData ret = 
 138:                 (SessionStateStoreData)
 139:                 storeType.InvokeMember(
 140:                 "GetItemExclusive",
 141:                 System.Reflection.BindingFlags.InvokeMethod | 
 142:                 System.Reflection.BindingFlags.Instance | 
 143:                 System.Reflection.BindingFlags.Public,
 144:                 null, store, args
 145:             );
 146:  
 147:             locked  = (bool)args[2];
 148:             lockAge = (TimeSpan)args[3];
 149:             lockId  = args[4];
 150:             actions = (SessionStateActions)args[5];
 151:  
 152:             return ret;
 153:         }
 154:         else
 155:             throw new Exception( "State not initialized" );
 156:     }
 157:  
 158:     public override void InitializeRequest( HttpContext context )
 159:     {
 160:         if ( store != null )
 161:         {
 162:             storeType.InvokeMember(
 163:                 "InitializeRequest",
 164:                 System.Reflection.BindingFlags.InvokeMethod | 
 165:                 System.Reflection.BindingFlags.Instance | 
 166:                 System.Reflection.BindingFlags.Public,
 167:                 null, store, new object[] { context }
 168:             );
 169:         }
 170:     }
 171:  
 172:     public override void ReleaseItemExclusive( 
 173:         HttpContext context, string id, object lockId )
 174:     {
 175:         if ( store != null )
 176:         {
 177:             storeType.InvokeMember(
 178:                 "ReleaseItemExclusive",
 179:                 System.Reflection.BindingFlags.InvokeMethod | 
 180:                 System.Reflection.BindingFlags.Instance | 
 181:                 System.Reflection.BindingFlags.Public,
 182:                 null, store, new object[] { context, id, lockId }
 183:             );
 184:         }
 185:     }
 186:  
 187:     public override void RemoveItem( 
 188:         HttpContext context, string id, 
 189:         object lockId, SessionStateStoreData item )
 190:     {
 191:         if ( store != null )
 192:         {
 193:             storeType.InvokeMember(
 194:                 "RemoveItem",
 195:                 System.Reflection.BindingFlags.InvokeMethod | 
 196:                 System.Reflection.BindingFlags.Instance | 
 197:                 System.Reflection.BindingFlags.Public,
 198:                 null, store, new object[] { context, id, lockId, item }
 199:             );
 200:         }
 201:     }
 202:  
 203:     public override void ResetItemTimeout( HttpContext context, string id )
 204:     {
 205:         if ( store != null )
 206:         {
 207:             storeType.InvokeMember(
 208:                 "ResetItemTimeout",
 209:                 System.Reflection.BindingFlags.InvokeMethod | 
 210:                 System.Reflection.BindingFlags.Instance | 
 211:                 System.Reflection.BindingFlags.Public,
 212:                 null, store, new object[] { context, id }
 213:             );
 214:         }
 215:     }
 216:  
 217:     public override void SetAndReleaseItemExclusive( 
 218:         HttpContext context, string id, SessionStateStoreData item, 
 219:         object lockId, bool newItem )
 220:     {        
 221:         if ( store != null )
 222:         {
 223:             AnalyzeSessionContent( id, item );
 224:  
 225:             storeType.InvokeMember(
 226:                 "SetAndReleaseItemExclusive",
 227:                 System.Reflection.BindingFlags.InvokeMethod | 
 228:                 System.Reflection.BindingFlags.Instance | 
 229:                 System.Reflection.BindingFlags.Public,
 230:                 null, store, new object[] { context, id, item, lockId, newItem }
 231:             );
 232:         }
 233:     }
 234:  
 235:     private void AnalyzeSessionContent( string id, SessionStateStoreData item )
 236:     {
 237:         // item.Items
 238:     }
 239:  
 240:     public override bool SetItemExpireCallback( 
 241:         SessionStateItemExpireCallback expireCallback )
 242:     {
 243:         if ( store != null )
 244:         {
 245:             return (bool)
 246:                 storeType.InvokeMember(
 247:                 "SetItemExpireCallback",
 248:                 System.Reflection.BindingFlags.InvokeMethod | 
 249:                 System.Reflection.BindingFlags.Instance | 
 250:                 System.Reflection.BindingFlags.Public,
 251:                 null, store, new object[] { expireCallback }
 252:             );
 253:         }
 254:         else
 255:             throw new Exception( "State not initialized" );
 256:     }
 257: }
 258:  

3 comments:

Nishith Prabhakar said...

Instead of delegating the method calls using InvokeMember, can't one simply declare "store" as "SessionStateStoreProviderBase" and invoke the wrapped "store" methods directly in each of the wrapper methods. Once the instance of InProcSessionStateStore has been created using Activator, and assigned to "store" it is just a "SessionStateStoreProviderBase" with exactly the same interface. Isn't it?

Wiktor Zychla said...

I guess that would make the code much more readable at least.

Nishith Prabhakar said...

I will have to take my comment back. The InvokeMember approach seems to be working, while in the direct invocation using the base class, for some reason, the ISessionStateCollection seems to become null inside the InProcSessionState object everytime SetAndReleaseExclusive is called - and the GetExclusive returns an empty session collection.