Web.config中的<sessionState>节点元素的描述,共有Off、InProc、StateServer、SQLServer四种模式。Off、InProc分别指“不启用”、“进程内保存(默认值)”,此两种模式没啥讲的,所谓 InProc就是把 Session保存在aspnet_wp.exe (Windows 2000解析 ASP.NET页面所用的进程)或w3wp.exe(Win2003的进程)中,一旦进程被中止或被重置,Session将丢失。
一、 引发 Session丢失的几种原因
动过手写代码的人都知道,Session丢失是比较常见的事。以下是本人这几年所遇到的,能够引发 Session丢失的原因,不敢说是百分百,丢失概率还是特别高的。错…,简直可以说是“相…当…”高哇 ^_^"
1、 存放 Session的电脑重启(废话,若这样都不丢,你神仙啊)
2、 InProc模式:aspnet_wp.exe或w3wp.exe在“任务管理器”中或其它情况下导致其进程被终止运行。
3、 InProc模式:修改 .cs文件后,编译了两次(只编译一次,有时不会丢失)
4、 InProc模式:修改了Web.config
5、 InProc模式,Windows 2003环境:应用程序池回收、停止后重启
6、 InProc模式:服务器上 bin目录里的 .dll文件被更新
以上列举的都是 InProc模式下,容易引发解析 ASP.NET应用程序重置的原因。是不是觉得很窝火?之前我也有这种感觉,慢慢就习惯啦,再后来就干脆不用这种模式了。于是乎,就有了使用下列两种模式的尝试,现写出来与大家一起分享。
二、 使用 StateServer保存 Session
StateServer模式的实质是,把Session存放在一个单独的进程里,此进程独立于aspnet_wp.exe或w3wp.exe。启用此服务后,在“任务管理器”中可以看到一个名为aspnet_state.exe的进程,下面开始说明一下设置的具体步骤:
1、 修改注册表(关键步骤,如下图)
运行regedit→打开注册表→找到HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/aspnet_state/Parameters节点→将AllowRemoteConnection的键值设置成“1”(1为允许,0代表禁止)→设置Port(端口号)
注意事项:
a)、若ASP.NET State Service正在运行,修改注册表内容后,则需要重新启动该服务
b)、注意端口号的键值是以十六进制储存的,可以使用十进制进行修改,42424是默认的端口
c)、AllowRemoteConnection的键值设置成“1”后,意味着允许远程电脑的连接,也就是说只要知道你的服务端口,就可享用你的ASP.NET State Service,即把 Session存放在你的电脑进程内,因此请大家慎用;键值为“0”时,仅有stateConnectionString为“tcpip=localhost: 42424”与“tcpip=127.0.0.1:42424”的情况,方可使用ASP.NET State Service
2、 开启ASP.NET State Service(如下图)
右键点击“我的电脑”→管理→服务与应用程序→服务→双击“ASP.NET State Service”→启动(可设为“自动”)
说明:只要安装了 .Net Framework v1.0/v1.1,都拥有此服务。
3、 更改Web.config
打开Web.config→找到<sessionState>节点内容
<sessionState
mode="InProc"
stateConnectionString="tcpip=127.0.0.1:42424"
sqlConnectionString="data source=127.0.0.1;Trusted_Connection=yes"
cookieless="false"
timeout="20"/>
→将其改为以下内容
<sessionStatemode="StateServer" stateConnectionString="tcpip=192.168.0.2:42424"timeout="20"/>
注意事项:
a)、设成StateServer后,必须要有对应的stateConnectionString
b)、注意 IP地址(可以是远程计算机 IP、计算机名称、域名)与端口号,端口号需与ASP.NET State Service的服务端口一致
4、 设定session,cookie 加密解密的方式,以实现共享
具体操作是在web.config中添加 machineKey,如下:
<machineKey validationKey="4F66E7BBC444FE7352B83801DEE8D86D1717CE13641BE08B4F10FAF2FFB6422078988DC3A608C12A52085433EE712E9C20C5DEE1C819AB967050EB02592E5BFB" decryptionKey="F5C3E443040B13A5B7CE6C1F2CA1134586777F7B5001F42F" validation="SHA1" />
machineKey的作用如下:
如果你的Asp.Net程序执行时碰到这种错误:“验证视图状态 MAC 失败。如果此应用程序由网络场或群集承载,请确保 <machineKey> 配置指定了相同的 validationKey 和验证算法。不能在群集中使用 AutoGenerate。”那么说明你没有让你的应用程序使用统一的machineKey,那么machineKey的作用是什么呢?按照MSDN的标准说法:“对密钥进行配置,以便将其用于对 Forms 身份验证 Cookie 数据和视图状态数据进行加密和解密,并将其用于对进程外会话状态标识进行验证。”也就是说Asp.Net的很多加密,都是依赖于machineKey里面的值,例如Forms 身份验证 Cookie、ViewState的加密。默认情况下,Asp.Net的配置是自己动态生成,如果单台服务器当然没问题,但是如果多台服务器负载均衡,machineKey还采用动态生成的方式,每台服务器上的machinekey值不一致,就导致加密出来的结果也不一致,不能共享验证和ViewState,所以对于多台服务器负载均衡的情况,一定要在每台站点配置相同的machineKey。
5、 如果同一个站点有多个域名,还需要保证客户端SessionID值唯一;
对于不同的域名:主域名、子域名、跨站点域名或跨服务器域名,用户在打开页面时会产生不同的SessionID,
为了使这些站点在用户登录时只登录一次,那我们就要解决SessionID的问题,必须使SessionID在这些共享Session的站点中只产生一次。而SessionID是存储在客户端的cookie之中键值为ASP.NET_SessionId的一个字符串(也可以存储在URL中,这里不作使介绍),为此只须使各站点存储的SP.NET_SessionId唯一即可。
因每个客户端在打开时会产生一个SessionID,为此我们要做的就是重置SessionID。我们可以在继承HttpModule,在结束请求时重写SessionID。
public class MakeSessionIDOneOnly : IHttpModule { private string m_RootDomain = string.Empty; #region IHttpModule Members public void Dispose() { } public void Init(HttpApplication context) { m_RootDomain = ConfigurationManager.AppSettings["RootDomain"]; Type stateServerSessionProvider = typeof(HttpSessionState).Assembly.GetType("System.Web.SessionState.OutOfProcSessionStateStore"); FieldInfo uriField = stateServerSessionProvider.GetField("s_uribase", BindingFlags.Static | BindingFlags.NonPublic); if (uriField == null) throw new ArgumentException("UriField was not found"); uriField.SetValue(null, m_RootDomain); context.EndRequest += new System.EventHandler(context_EndRequest); } void context_EndRequest(object sender, System.EventArgs e) { HttpApplication app = sender as HttpApplication; for (int i = 0; i < app.Context.Response.Cookies.Count; i++) { if (app.Context.Response.Cookies[i].Name == "ASP.NET_SessionId") { app.Context.Response.Cookies[i].Domain = m_RootDomain; } } } #endregion }
三、 将 Session放入 SQLServer保存
SQLServer模式就是,把Session存放在SQL Server数据库里(注意不是Oracle,动动脚趾都能猜到原因啦),下面开始说明一下设置的具体步骤:
1、 启动相关的数据库服务(如图)
运行SQL Server服务管理器→启动SQL Server(最好设为开机自动运行)→启动SQL Server Agent服务(最好设为开机自动运行)
注意事项:
a)、注意启动顺序,也可通过下列方式设置:右键点击“我的电脑”→管理→服务与应用程序→服务→找到“MSSQLSERVER”与“SQLSERVERAGENT”→启动并设置启动类型为“自动”
b)、SQL Server Agent在此处的作用是清除数据库中已过期的 Session
2、 建立存放 Session的DataBase
运行“SQL查询分析器”→使用“sa”或是拥有“master”的db_owner权限的用户登录数据库→打开查询文件C:/WINNT/Microsoft.NET/Framework/v1.1.4322/InstallSqlState.sql(存放在 Windows 系统目录的 .Net安装目录下可找到)→直接运行该 sql脚本→刷新数据库即可看到名为ASPState的 DataBase
3、 建立连接数据库ASPState的用户,并为此用户授权(此步骤可跳过)
进行此步的原因是:一是不想在Web.config中出现sa的密码;二是tempdb在数据库启动后仅保留sa一个帐号的使用权限,其余帐号的权限统统被清除,但保存 Session又需要用到此DataBase;
A)、运行 SQL Server的企业管理器→展开数据库的安全性→右击“登录”→新建“登录”→输入“名称”→选择“SQL Server 身份验证”→输入“密码”→指定“数据库”→点击“数据库访问”→勾选“ASPState”→选中“db_owner”角色→点击“确定”→再一次输入“密码”→点击“确定”后即可建立ASPState的用户(此处建立名为“SessionStateUser”,密码为“123456”的测试用户)
B)、运行 SQL Server的企业管理器→展开“管理”→展开“SQL Server 代理”→右击“作业”→点击“新建作业”→输入“名称”(此例为GrantSessionUser)→点击标签“步骤”→新建→输入“步骤名”(此例为Grant01)→选择数据库“tempdb”→编写 SQL 脚本“execsp_adduser'SessionStateUser', 'SessionUser' ,'db_owner'”→确定→点击标签“调度”→新建→输入“名称”(此例为Start01)→选择类型“SQL Server 代理启动时自动启动”→确定→最后点击“确定”新增完毕
C)、也可运行以下脚本一次性搞定以上 A、B两个步骤
/******脚本开始******/
--新建数据库帐号 SessionStateUser,默认登录 ASPState
EXECsp_addlogin 'SessionStateUser', '123456', 'ASPState'
useASPState --切换 DataBase
--将 SessionStateUser授予 db_owner的权限
exec sp_adduser 'SessionStateUser','SessionUser','db_owner'
usemaster --切换 DataBase
BEGIN TRANSACTION
/******声明变量******/
DECLARE@JobIDBINARY(16)
DECLARE@ReturnCodeINT
SELECT@ReturnCode= 0
--若没有,则添加作业的分类
IF(SELECTCOUNT(*)FROMmsdb.dbo.syscategoriesWHEREname=N'[Uncategorized (Local)]') < 1
EXECUTEmsdb.dbo.sp_add_category @name=N'[Uncategorized (Local)]'
--新建作业
EXECUTE@ReturnCode=msdb.dbo.sp_add_job --调用存储过程 sp_add_job
@job_id=@JobIDOUTPUT, --将返回的 JobID,赋值给变量
@job_name=N'GrantSessionUser', --作业名称
@owner_login_name= NULL, --默认为当前用户所有
@description=null,
@category_name=N'[Uncategorized (Local)]', --作业分类归属
@enabled=1, --是否启用
@notify_level_email=0,
@notify_level_page=0,
@notify_level_netsend=0,
@notify_level_eventlog =0,
@delete_level=0
IF(@@ERROR<>0OR@ReturnCode<>0)GOTOQuitWithRollback--出错则回滚
--新建步骤
EXECUTE@ReturnCode=msdb.dbo.sp_add_jobstep--调用存储过程 sp_add_jobstep
@job_id= @JobID, --传入刚刚新建的 JobID
@step_id=1,
@step_name=N'Grant01', --步骤名称
@command=N'exec sp_adduser ''SessionStateUser'', ''SessionUser'' ,''db_owner''',
--需要执行的 SQL脚本(注意用两个连续的单引号表示 SQL中的单引号)
@database_name=N'tempdb',--执行上述 SQL 所用的 DataBase
@server=N'',
@database_user_name=N'',
@subsystem=N'TSQL', --执行类型为“Transact-SQL脚本”
@cmdexec_success_code=0,
@flags=0,
@retry_attempts=0,
@retry_interval=1,
@output_file_name=N'',
@on_success_step_id=0,
@on_success_action=1,
@on_fail_step_id=0,
@on_fail_action=2
IF(@@ERROR<>0OR@ReturnCode<>0)GOTOQuitWithRollback
--新建调度
EXECUTE @ReturnCode= msdb.dbo.sp_add_jobschedule
@job_id=@JobID,
@name= N'Start01', --调度名称
@enabled=1,
@freq_type= 64 --“64”表示当 SQLServerAgent 服务启动时运行
IF(@@ERROR <>0OR@ReturnCode<> 0)GOTOQuitWithRollback
--将新建的作业添加到本地数据库
EXECUTE@ReturnCode=msdb.dbo.sp_add_jobserver@job_id=@JobID,@server_name=N'(local)'
IF(@@ERROR<>0OR@ReturnCode<>0)GOTOQuitWithRollback
COMMITTRANSACTION
GOTO EndSave
QuitWithRollback:
IF(@@TRANCOUNT>0)ROLLBACKTRANSACTION
EndSave:
/******脚本结束******/
4、 设置Web.config内容
打开Web.config→找到<sessionState>节点内容→修改为以下内容即可:
<sessionStatemode="SQLServer" sqlConnectionString="data source=192.168.0.2; user id=SessionStateUser; password=123456"timeout="20"/>
注意事项:
a)、sqlConnectionString中不能出现initial catalog选项
b)、SQL Server Agent在此处的作用是清除数据库中已过期的 Session
c)、你若跳过了第三步,则user id需要用sa进行登录
d)、若sqlConnectionString为“data source=127.0.0.1;Trusted_Connection=yes”,则使用本地计算机ASPNET(Windows 2000 系统帐户)或Network Service(Windows 2003 系统帐户)的身份登录数据库。要是数据库不允许上述用户登录,则报错;同样,即使上述帐户能成功登录,也要分配其tempdb的权限,理由是 Session 是保存在tempdb中的,若没有该DataBase的存取权限是行不滴。见下图: