實現ASP.NET網站之間的單點登陸(跨域共享Session)

本文實現不同的WebSite(必須是ASP.NET 應用程序)之間共享Session依據的是使用進程外 SQL Server 數據庫來存儲狀態信息。

 

爲了方便理解,下面對本文中要使用的對象進行定義:

Website1:  dealer      (由VS2003 .net framework v1.1.4322開發)

Website2:  DealerV2           (VS2010 .net framework v4.0.30319開發)

(dealerDealerV2都是ASP.NET 應用程序)

 

將會話狀態保存到SQL SERVERASP.NET 應用程序的一個特有的屬性(側面證明微軟確實很牛X),但這需要在ASP.NET 應用程序的配置文件web.config中對sessionState 元素進行配置,這個配置方法我將放在第三部分進行說明,現在瞭解一下sessionState元素的作用

 

以下內容來自MSDN :

sessionState 元素配置當前應用程序的會話狀態設置。

新客戶端在開始與 Web 應用程序交互時,會發出一個會話 ID,並且該 ID 將與會話有效期間從同一客戶端發出的所有後續請求關聯。此 ID 用於在不同的請求中保持與客戶端會話關聯的服務器端狀態。sessionState 元素控制 ASP.NET 應用程序如何爲每個客戶端建立並保持這種關聯。

 

以下爲配置dealerDealerV2共享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+AppID16進製表示的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存儲的類定義前加上這個標識,問題解決!

 

dealerSystem.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放到dealerDealerV2相同的命名空間MIS.USER

 

比較棘手的是用戶首先從dealer登陸,存對象的時候可能是dealer的程序集,後跳轉到DealerV2,需要從Session中取DataRow對象,這個時候報錯了無法找到程序集 “dealer … PublicKeyToken=null”

OK,我將dealer.dll 拷貝到DealerV2bin目錄下,還是報錯,因爲DataRowSub在兩個位置出現了。

 

最終找個一個解決方法:

DataRow的序列化類DataRowSub做爲一個組件放到兩個Project中(生成一個Dll.

但要注意兩點:

1.   如果dealerDealerV2的開發工具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')

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章