本文實現不同的WebSite(必須是ASP.NET 應用程序)之間共享Session依據的是使用進程外 SQL Server 數據庫來存儲狀態信息。
爲了方便理解,下面對本文中要使用的對象進行定義:
Website1: dealer (由VS2003 .net framework v1.1.4322開發)
Website2: DealerV2 (由VS2010 .net framework v4.0.30319開發)
(dealer和DealerV2都是ASP.NET 應用程序)
將會話狀態保存到SQL SERVER是ASP.NET 應用程序的一個特有的屬性(側面證明微軟確實很牛X),但這需要在ASP.NET 應用程序的配置文件web.config中對sessionState 元素進行配置,這個配置方法我將放在第三部分進行說明,現在瞭解一下sessionState元素的作用
以下內容來自MSDN :
sessionState 元素配置當前應用程序的會話狀態設置。
新客戶端在開始與 Web 應用程序交互時,會發出一個會話 ID,並且該 ID 將與會話有效期間從同一客戶端發出的所有後續請求關聯。此 ID 用於在不同的請求中保持與客戶端會話關聯的服務器端狀態。sessionState 元素控制 ASP.NET 應用程序如何爲每個客戶端建立並保持這種關聯。
以下爲配置dealer和DealerV2共享SESSION的幾大步驟:
1 在SQL SERVER上建立數據庫,存儲服務端的會話狀態信息。
執行C:/WINDOWS/Microsoft.NET/Framework/v1.1.4322/InstallPersistSqlState.sql建庫腳本,這將創建一個具有存儲過程的名爲 ASPState 的數據庫。
2 添加ASP.NET會話狀態
C:/WINDOWS/Microsoft.NET/Framework/v4.0.30319/aspnet_regsql.exe -sstype c -ssadd -d ASPState -U sa -P mis -S 127.0.0.1
使用自定義的數據庫ASPState來存儲會話狀態信息。
執行這個腳本除了註冊ASP.NET會話狀態SQL 服務器之外,還對ASPState中的存儲過程進行了更改。
在 SQL Server 上卸載 ASP.NET 會話狀態功能,命令如下:
aspnet_regsql.exe -ssremove -U sa -P mis -S 127.0.0.1
不同的應用程序會在ASPStateTempApplications中註冊不同的數據,從而在ASPStateTempSessions中有不同的Session,如果要實現Session共享,可以用欺騙SqlServer的方法:用某種方法使得不同的應用程序訪問數據庫時用相同的App!爲了達到這個目的,分析[dbo].[TempGetAppID]這個存儲過程。
注:數據庫中SessionId的值,是“頁面SessionID+AppID(16進製表示的8位數)”組合而成的。
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
ALTER PROCEDURE [dbo].[TempGetAppID]
@appName tAppName,
@appId int OUTPUT
AS
--SET @appName = LOWER(@appName)
SET @appName = 'dealDealerV2'
SET @appId = NULL
SELECT @appId = AppId
FROM [ASPState].dbo.ASPStateTempApplications
WHERE AppName = @appName
IF @appId IS NULL BEGIN
BEGIN TRAN
SELECT @appId = AppId
FROM [ASPState].dbo.ASPStateTempApplications WITH (TABLOCKX)
WHERE AppName = @appName
IF @appId IS NULL
BEGIN
EXEC GetHashCode @appName, @appId OUTPUT
INSERT [ASPState].dbo.ASPStateTempApplications
VALUES
(@appId, @appName)
IF @@ERROR = 2627
BEGIN
DECLARE @dupApp tAppName
SELECT @dupApp = RTRIM(AppName)
FROM [ASPState].dbo.ASPStateTempApplications
WHERE AppId = @appId
RAISERROR('SQL session state fatal error: hash-code collision between applications ''%s'' and ''%s''. Please rename the 1st application to resolve the problem.',
18, 1, @appName, @dupApp)
END
END
COMMIT
END
RETURN 0
棕色的代碼是腳本的修改部分
3 配置SessionState
file: web.config
<sessionState mode="SQLServer" sqlConnectionString="Data Source=127.0.0.1;Initial Catalog=ASPState;User Id=sa;Password=mis;" allowCustomSqlDatabase="true" cookieless="false" timeout="60" />
此處的Initial Catalog最好與註冊ASP.NET會話狀態SQL 數據庫一致。
注:修改完存儲過程後,需要把ASP.NET網站程序重新啓動一下(F5)
在StateServer或者SQLServer模式的時候保存在Session裏面的對象必須支持序列化!否則會報錯“無法序列化會話狀態”。如果是內置的類創建的對象,則必須是支持序列化的對象;如果是自己定義的類創建,則可以在定義該類的時候標識如下:
[Serializable]
public class YourClass
{
//class code
}
[Serializable] 標識該類創建的對象是可以序列化的。
於是,在C#裏你需要session存儲的類定義前加上這個標識,問題解決!
但dealer將System.Data.DataRow數據類型的對象存儲到Session中,而DataRow 又是不支持序列化的。
存對象:
DataRow drUser = myUser.getUserInfo(myConn, strLoginid);
Session.Add("UserInfo", drUser);
取對象:
DataRow drUser = (DataRow)Session["UserInfo"];
因爲DataRow類型爲protected internal,不支持繼承,所以無法通過預定義的數據類型之間轉換(即存的是子類,取出來的時候通過強制類型轉換轉成父類)。
所以嘗試用用戶定義的數據類型轉換
//namespace System.Data
namespace MIS.USER
{
[Serializable]
public class DataRowSub
{
public Hashtable haspMap;//以key/value方式存儲UserInfo對象
public DataTable dt;//使用用戶定義的數據類型轉換的時候生成空的DataRow對象
public DataRowSub()
{
}
public DataRowSub TransferDataType(DataRow value1)
{
haspMap = new Hashtable();
foreach (DataColumn dc in value1.Table.Columns)
{
haspMap.Add(dc.ColumnName, value1[dc.ColumnName]);
}
dt = value1.Table;
return this;
}
//對沒有類的繼承關係的兩種類型定義一種隱式轉換操作(顯示轉換也OK)
public static implicit operator DataRow(DataRowSub value1)
{
DataRow drTmp = value1.dt.NewRow();
foreach (DictionaryEntry de in value1.haspMap)
{
drTmp[de.Key.ToString()] = de.Value;
}
return drTmp;
}
//索引指示器,使DataRowSub有與DataRow一致的取值方式
public string this[string columnName]
{
get
{
return (haspMap[columnName]).ToString();
}
}
}
//僅作DataRowSub類型的對象聲明用
public class JustForDeclareDataRowSub
{
public static DataRowSub drs;
}
}
定義JustForDeclareDataRowSub類的原因是方便dealer的代碼修改。
因爲我嘗試後發現:
DataRow myDR = (DataRow)((DataRowSub)Session["UserInfo"]);
這種寫法對於用戶定義的數據類型轉換根本就無效,系統不會對Session["UserInfo"]進行用戶定義的強制轉換,而下面的寫法卻可以轉換成功.
DataRowSub DrsInSession = (DataRowSub)Session["UserInfo"];
DataRow myDR = (DataRow)DrsInSession;
所以整個項目將作如下更改:
將DataRow drUser = (DataRow)Session["UserInfo"];
替換成
DataRowSub DrsInSession = (DataRowSub)Session["UserInfo"];
DataRow drUser = (DataRow)DrsInSession;
爲了方便代碼的更新,所以定義了類型JustForDeclareDataRowSub
DataRow drUser = (DataRow)(JustForDeclareDataRowSub.drs =(DataRowSub)Session["UserInfo"]);
將以上的序列化類DataRowSub放到dealer和DealerV2相同的命名空間MIS.USER
比較棘手的是用戶首先從dealer登陸,存對象的時候可能是dealer的程序集,後跳轉到DealerV2,需要從Session中取DataRow對象,這個時候報錯了—“無法找到程序集 “dealer … PublicKeyToken=null”。
OK,我將dealer.dll 拷貝到DealerV2的bin目錄下,還是報錯,因爲DataRowSub在兩個位置出現了。
最終找個一個解決方法:
將DataRow的序列化類DataRowSub做爲一個組件放到兩個Project中(生成一個Dll).
但要注意兩點:
1. 如果dealer和DealerV2的開發工具Visual Studio版本不一樣,這個Dll一定要用較低版本的VS生成。
2. Dll的命名空間最好與DataRow一致,這樣代碼中涉及到往Session中存取DataRow的地方就不用額外添加引用了。
如果想在ASPState中管理不同的Session組,例如WebApp1和WebApp2共享會話狀態,WebApp3和WebApp4共享會話狀態,它們的會話狀態
都存儲在ASPState中,可以對ASPState中的存儲過程TempGetAppID做如下修改:
1. ASPStateTempApplications增加字段 SessionGroupId int
2.修改TempGetAppID
set ANSI_NULLS ON
set QUOTED_IDENTIFIER ON
go
ALTER PROCEDURE [dbo].[TempGetAppID]
@appName tAppName,
@appId int OUTPUT
AS
SET @appName = LOWER(@appName)
SET @appId = NULL
SELECT @appId = SessionGroupId
FROM [ASPState].dbo.ASPStateTempApplications
WHERE AppName = @appName
IF @appId IS NULL BEGIN
BEGIN TRAN
SELECT @appId = SessionGroupId
FROM [ASPState].dbo.ASPStateTempApplications WITH (TABLOCKX)
WHERE AppName = @appName
IF @appId IS NULL
BEGIN
DECLARE @SGrpId int
EXEC GetHashCode @appName, @appId OUTPUT
SET @SGrpId = @appId
IF RTRIM(@appName) = '/lm/w3svc/1/root/dealer' OR RTRIM(@appName) = '/lm/w3svc/1/root/dealerv2'
BEGIN
SET @SGrpId = 168
END
INSERT [ASPState].dbo.ASPStateTempApplications
VALUES
(@appId, @appName,@SGrpId)
SET @appId = @SGrpId
IF @@ERROR = 2627
BEGIN
DECLARE @dupApp tAppName
SELECT @dupApp = RTRIM(AppName)
FROM [ASPState].dbo.ASPStateTempApplications
WHERE AppId = @appId
RAISERROR('SQL session state fatal error: hash-code collision between applications ''%s'' and ''%s''. Please rename the 1st application to resolve the problem.',
18, 1, @appName, @dupApp)
END
END
COMMIT
END
RETURN 0
調試過程中可以會發生死鎖,下面是解決死鎖的一些常用SQL:
DBCC INPUTBUFFER(52)
select * from sys.dm_os_waiting_tasks
--進程號1--50是SQL Server系統內部用的,進程號大於50的纔是用戶的連接進程.
exec sp_who2
--查看SQL Server數據庫裏的鎖的情況
exec sp_lock
--切換登陸用戶
use master
exec ('kill 54')