怎么使用Session本身的问题,这个就不必多说了吧。在这里就谈谈Session的管理机制。ASP.NET的优秀之处就在于它能充分利用整个.NET框架所提供的优秀的基础结构和底层操作,而大量设计模式的应用则大大加强了ASP.NET的可扩展性和可伸缩。当然在Session的管理方面.NET框架也同样为其提供了巨大的灵活性和可扩展性。
ASP.NET为Session的管理提供了5种可选模式(基于Provider模型):1.不使用(Off),2.进程内(InProc),3.状态服务器(StateServer),4.SQLServer数据库(SQLServer),5.自定义(Custom)。默认情况下ASP.NET使用进程内模式管理Session状态。一般情况下基于小型应用的ASP.NET网站使用进程内管理Session状态已经足够了。如果我们需要在一个拥有多个子网站频道的大型网站中使用Session状态管理,那我们就需要考虑使用状态服务器或SQLServer的模式来管理Session的状态了。同样的需求也可能出现在负载均衡情况下的网站群集条件中。关于如何使用状态服务器或SQLServer的模式来管理大型网站的Session状态的介绍和使用详细在这里也就不具体介绍说明了,感兴趣的话可以参考MSDN文档或各位大牛的技术博文。^-^
在一年前的一个项目中我遇到如此情景,项目是由一个主体频道站点和多个子频道站点组成。项目中有关用户的管理被设计到一个单独用户管理中心站点统一管理。鉴于之上的设计需求管理整个站点的Session状态我自然而然就想到需要使用状态服务器或SQLServer的模式来实现Session共享。于是参考了一段时间MSDN文档后决定使用状态服务器来实现(同时也由于项目本身自己的一些特点)。于是兴致勃勃的继续着之后的设计,但当项目即将交付的时候却被告知当前生产环境中只有三台服务器可供项目部署。由于当初参考MSDN文档的时候我清楚的记得使用状态服务器管理Session状态时,在IIS托管的网站及子网站都需要相同的网站标识符才能在状态服务器中定位正确的Session状态对象,而现在多个网站可能需要被部署到同一台机器的同一个IIS中,大家都知道每个IIS中的网站标识符都是唯一的。这下子我犯难了,于是苦思冥想了整整2天时间,最后想到了下面这个另类用法来解决此问题。
由于框架为我提供的状态服务器模式不能满足我的要求,因此我需要使用自定义Session管理模式。用于拦截IIS向状态服务器请求的消息,模拟所有频道站点的请求都使用相同的网站标识符。在这里我按照Session管理的提供者扩展模型实现了一个自定义的Session管理类,用来给系统提供的状态服务器管理类做一个对象适配,在内部同过反射技术来创建一个System.Web.SessionState.OutOfProcSessionStateStore类的对象实例并修改对象的部分属性来满足项目的需求,代码类定义如下:
Code
//本类使用对象适配器模式修改系统默认设置,使单个IIS中多个应用程序共用同一状态session
public class CustomOutOfProcSessionStateStore : SessionStateStoreProviderBase
{
private SessionStateStoreProviderBase _sessionStateStore = null;
public OutOfProcSessionStateStore()
{
Assembly assembly = Assembly.GetAssembly(typeof(System.Web.SessionState.HttpSessionState));
_sessionStateStore =
(SessionStateStoreProviderBase)assembly.CreateInstance("System.Web.SessionState.OutOfProcSessionStateStore");
}
public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
_sessionStateStore.Initialize(name, config);
Type storeType = _sessionStateStore.GetType();
//系统指定默认路径变量名称
string baseUriName = "s_uribase";
//获取系统默认路径设置
string oldBaseUri = (string)storeType.InvokeMember(baseUriName,
BindingFlags.GetField | BindingFlags.NonPublic | BindingFlags.Static,
null,
null,
new object[] { }
);
//指定系统ApplicationId,这里的888就是IIS中的网站标识符
string appId = "/LM/W3SVC/888/ROOT";
//获取配置加密key
string hashKey = (string)typeof(System.Web.Configuration.MachineKeySection).InvokeMember("HashAndBase64EncodeString",
BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Static,
null,
null,
new object[] { appId }
);
//获取新的session检索基路径
string newBaseUri = appId + "(" + hashKey + ")/";
//修改系统默认路径设置
storeType.InvokeMember(baseUriName,
BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Static,
null,
null,
new object[] { newBaseUri }
);
base.Initialize(name, config);
}
public override SessionStateStoreData CreateNewStoreData(HttpContext context, int timeout)
{
return _sessionStateStore.CreateNewStoreData(context, timeout);
}
public override void CreateUninitializedItem(HttpContext context, string id, int timeout)
{
_sessionStateStore.CreateUninitializedItem(context, id, timeout);
}
public override void Dispose()
{
_sessionStateStore.Dispose();
}
public override void EndRequest(HttpContext context)
{
_sessionStateStore.EndRequest(context);
}
public override SessionStateStoreData GetItem(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
{
return _sessionStateStore.GetItem(context, id, out locked, out lockAge, out lockId, out actions);
}
public override SessionStateStoreData GetItemExclusive(HttpContext context, string id, out bool locked, out TimeSpan lockAge, out object lockId, out SessionStateActions actions)
{
return _sessionStateStore.GetItemExclusive(context, id, out locked, out lockAge, out lockId, out actions);
}
public override void InitializeRequest(HttpContext context)
{
_sessionStateStore.InitializeRequest(context);
}
public override void ReleaseItemExclusive(HttpContext context, string id, object lockId)
{
_sessionStateStore.ReleaseItemExclusive(context, id, lockId);
}
public override void RemoveItem(HttpContext context, string id, object lockId, SessionStateStoreData item)
{
_sessionStateStore.RemoveItem(context, id, lockId, item);
}
public override void ResetItemTimeout(HttpContext context, string id)
{
_sessionStateStore.ResetItemTimeout(context, id);
}
public override void SetAndReleaseItemExclusive(HttpContext context, string id, SessionStateStoreData item, object lockId, bool newItem)
{
_sessionStateStore.SetAndReleaseItemExclusive(context, id, item, lockId, newItem);
}
public override bool SetItemExpireCallback(SessionStateItemExpireCallback expireCallback)
{
return _sessionStateStore.SetItemExpireCallback(expireCallback);
}
}
整个类中最重要的方法是Initialize方法,其他的只是做一下简单的对象方法适配以保证和框架提供的类保持相同的逻辑功能。其实在整个拦截过程中我们只需要修改baseUriName的值,如何修改baseUriName的值已经在类的方法定义中做了注释说明,大家可以参照类型定义了解详细。
类型定义好以后我们需要做的代码工作就完毕了,接下来要做的只是把这个类型引入到各个频道站点项目下,当然也可以直接把类型复制到AppCode目录下,也可以把它单独放在一个程序集中单独编译并引用到各个频道站点项目下。 接下我们需要做的工作就是修改各个站点下Web.Config文件中的Session配置节点,配置如下:
<sessionState
mode="Custom"
cookieless="false"
timeout="15"
stateConnectionString="tcpip=ip:端口"
customProvider="OutOfProcSessionProvider"
stateNetworkTimeout="300">
<providers>
<add name="OutOfProcSessionProvider" type="CustomOutOfProcSessionStateStore"/>
</providers>
</sessionState>
在这里特别要注意的是我们还需要添加machineKey配置节用来在Initialize方法中使用相同的key值重新加密解密ApplicationId。
<machineKey
validationKey="78AE3850338BFADCE59D8DDF58C9E4518E7510149C46142D7AAD7F1AD49D95D4"
decryptionKey="5FC88DFC24EA123C"
validation="SHA1"/>
最后在各个站点中执行相同的配置过程就可以实现同一IIS中用状态服务器模式实现Session状态共享了。^-^
当然如果以后条件许可下,我们只需要简单的修改下配置就能把Session管理还原到框架提供的状态服务器管理模型中,非常之方便!