2012-10-29 123 views
8

我正在設置SQL Server會話狀態的一個非常基本的演示,但在使其工作時遇到了一些麻煩。我試圖用IIS 7.5和SQL Server 2008 R2在本地運行Windows 7進行測試。在Web應用程序間共享SQL Server會話狀態

最終,我需要一種方法來跟蹤登錄到在幾個不同的Web服務器之間進行負載平衡的系統的用戶數量。所以我需要每次用戶登錄或註銷時更新會話變量(存儲在SQL中)。所以會話ID總是可以不同。這可能嗎?

這是我到目前爲止已經完成:

  1. 創建了兩個新的IIS(1和Site2)網站
  2. 創建兩個新的Web應用程序項目在VS 2010中(1和Site2)
  3. 在這兩個站點的Default.aspx頁面上,我創建了一個按鈕,單擊它時會將「Site1」或「Site2」(取決於站點)保存到名爲「LastSiteUsed」的會話變量中。還有另一個按鈕,單擊它時,將讀取「LastSiteUsed」會話變量的值並將其顯示在標籤中。
  4. 在SQL中,我創建了一個名爲dbSessionTest的新數據庫。然後我按照MSDN to install the Session State Database using the aspnet_regsql tool上的說明操作。
  5. 在我的兩個Web應用程序的Web.config文件中,我已經添加了以下下<System.Web>

<sessionState mode="SQLServer" sqlConnectionString="server=.\sqlexpress;database=dbSessionTest;uid=myUsername;pwd=myPassword" cookieless="false" timeout="20" allowCustomSqlDatabase="true"/>

這兩個網站的功能正常,但他們似乎並不爲分享會議。例如,當我點擊我的按鈕以從Site1保存會話變量時,我希望能夠從Site2中讀取該值,但它不起作用。 Site2將只返回「Site2」,而Site1將只返回「Site1」。

有沒有人有任何想法,我可能做錯了什麼?我錯在認爲我應該能夠從Site2讀取由Site1設置的值嗎?

UPDATE:

我可以看到會話數據獲得存儲在SQL Management Studio中ASPStateTempSessions表,但每個站點仍然只能看到它寫的值。每個站點都設置會話變量是這樣的:

Session("LastSiteUsed") = "Site2" 

這兩個網站的檢索像值:

lblValue.Text = "Value from Session: " & Session("LastSiteUsed").ToString 

我需要訪問存儲在SQL Server會話變量不同的做到這一點?

更新2:

我使用默認的數據庫,ASPState的,這是運行此命令創建的嘗試:

aspnet_regsql.exe -S MyServerName -E -ssadd -sstype p 

然後簡化我的每一個網頁的。配置文件,如:

<sessionState mode="SQLServer" sqlConnectionString="Data Source=.\sqlexpress;User ID=myUsername;Password=myPassword" cookieless="false" timeout="20"/> 

但同樣,沒有運氣。我希望能夠從Site1中設置會話變量,然後從Site2中讀取值,但它不起作用。再次,我可以看到ASPStateTempSessions表中顯示的條目,所以我知道它們輸入正確。我注意到的一件事是他們有不同的會話ID?

有什麼我需要做不同的事情來確保在設置/讀取同一個會話變量時在我的站點之間使用相同的會話ID?

UPDATE 3:

我已經從this article其中修改一個SP和用於分組到ASPStateTempApplications表添加一個柱,然後進行操作。這工作,但前提是兩個我的網站都在同一瀏覽器中打開(使用標籤)。我需要能夠在IE中打開Site1,將值保存到我的會話變量中,關閉瀏覽器,在Chrome中打開Site2並讀取值。從我用SQL Server會話狀態讀取的所有內容...這應該是可能的。

UPDATE 4 - 解決方案:

我接着@SilverNinja提供this article答案。我通過命令提示符重新創建了ASPState數據庫(以在更新#3中撤銷我的SP更改),然後根據鏈接的答案修改TempGetAppID SP。我也更新我的兩個Web.config文件中,從那篇文章以及答案一致:

<sessionState mode="SQLServer" 
       sqlConnectionString="Data Source=.\sqlexpress;User ID=myUsername;Password=myPassword;Application Name=CacheTest"/> 

我也同時包括在Web.config文件中相同的計算機密鑰:

<machineKey validationKey="59C42C5AB0988049AB0555E3F2128061AE9B75E3A522F25B34A21A46B51F188A5C0C1C74F918DFB44C33406B6E874A54167DFA06AC3BE1C3FEE8506E710015DB" decryptionKey="3C98C7E33D6BC8A8C4B7D966F42F2818D33AAB84A81C594AF8F4A533ADB23B97" validation="SHA1" decryption="AES" /> 

現在我可以(使用IE)打開我的兩個站點,從站點1設置一個值,並從站點2讀取它(反之亦然)。當我檢查表時,只有一個SessionID存在(兩個站點正確使用相同的會話)。在開啓新瀏覽器(例如Chrome)之前,我的想法是錯誤的,會使用相同的會話 - 新瀏覽器將啓動它自己的會話。在負載平衡的情況下,這不應該導致任何問題。

+0

'Application Name = CacheTest'對於所有Web應用程序(WebSites)都是一樣的嗎?或**不同的值**? – Kiquenet

回答

7

您遇到的問題是跨ASP.NET應用程序共享會話狀態。 SQL Server會話提供程序不支持此功能。看到這個SO post regarding what changes to make to SQL Server session provider to support cross-application sessions

我不會使用會話來管理這個共享狀態(即LastSiteUsed)。此信息應與用戶配置文件存儲(Membership,Profile provider,自定義數據庫等)保持一致。由於會話可能會過期 - 跨應用程序使用共享會話不是一種可靠的機制來跟蹤持久性用戶特定狀態。如果持久性並不重要,那麼將AppFabric Cache用於內存中的跨應用程序共享狀態並不重要。

+0

感謝您的回覆!如果會話過期(如果我理解正確),我認爲應該可以,因爲我最終只是跟蹤登錄的併發用戶數。也就是說,您的第一個鏈接是否足以完成我所需要的操作(具體來說該鏈接的答案)? – lhan

+0

FWIW我繼續嘗試解決方案,但沒有奏效。再想一想,情況就不一樣了。他們想在同一臺服務器上共享一個會話。我需要在存儲在SQL中的多個負載平衡服務器之間共享一個會話變量。這似乎應該是可能的? – lhan

+0

如果服務器是負載均衡的 - 你只需要確保它們的'web.config'或'machine.config'中的**機器密鑰都是**,以供訪問共享會話的應用程序使用。無論是同一臺服務器還是不同的服務器 - 只要密鑰相同,無關緊要。 – SliverNinja

2

對於任何想要或需要在不修改數據庫存儲過程的情況下解決此問題的人,請考慮此方法(不適用於膽小鬼)。

其基本思想是存在System.Web.SessionState.SqlSessionStateStore.s_singlePartitionInfo中存儲的SqlSessionStateStore.SqlPartitionInfo的靜態實例,我通過調用InitSqlInfo進行初始化。一旦這個實例被初始化,我設置內部_appSuffix字段的值。在設置_appSuffix字段之前,需要初始化實例,否則在初始化過程中我的自定義值將被覆蓋。我使用與ASP.NET狀態數據庫中相同的哈希函數來提供應用程序名稱中的哈希代碼。

請注意,這是一樣討厭,因爲它是完全依賴於內部實現細節,可能隨時改變。但是,我不知道任何實際的選擇。您自行決定使用並承擔風險。

現在我已經給出了我的免責聲明,如果.NET BCL中的這個實現確實發生了變化,我將感到驚訝,因爲這種身份驗證方法正在被替換,並且我沒有看到任何微軟在那裏修改的理由。

/* 
* This code is a workaround for the fact that session state in ASP.NET SqlServer mode is isolated by application. The AppDomain's appId is used to 
* segregate applications, and there is no official way exposed to modify this behaviour. 
* 
* Some workarounds tackle the problem by modifying the ASP.NET state database. This workaround approaches the problem from the application code 
* and will be appropriate for those who do not want to alter the database. We are using it during a migration process from old to new technology stacks, 
* where we want the transition between the two sites to be seamless. 
* 
* As always, when relying on implementation details, the reflection based approach used here may break in future/past versions of the .NET framework. 
* Test thoroughly. 
* 
* Usage: add this to your Global.asax: 
*  protected void Application_BeginRequest() 
*  { 
*   SessionStateCrossApplicationHacker.SetSessionStateApplicationName("an application"); 
*  } 
*/ 

using System; 
using System.Data.SqlClient; 
using System.Globalization; 
using System.Reflection; 
using System.Web.SessionState; 

public static class SessionStateCrossApplicationHacker 
{ 
    static string _appName; 
    static readonly object _appNameLock = new object(); 

    public static void SetSessionStateApplicationName(string appName) 
    { 
     if (_appName != appName) 
     { 
      lock (_appNameLock) 
      { 
       if (_appName != appName) 
       { 
        SetSessionStateApplicationNameOnceOnly(appName); 

        _appName = appName; 
       } 
      } 
     } 
    } 

    static void SetSessionStateApplicationNameOnceOnly(string appName) 
    { 
     //get the static instance of SqlSessionStateStore.SqlPartitionInfo from System.Web.SessionState.SqlSessionStateStore.s_singlePartitionInfo 
     var sqlSessionStateStoreType = typeof (SessionStateMode).Assembly.GetType("System.Web.SessionState.SqlSessionStateStore"); 
     var sqlSessionStatePartitionInfoInstance = GetStaticFieldValue(sqlSessionStateStoreType, "s_singlePartitionInfo"); 
     if (sqlSessionStatePartitionInfoInstance == null) 
      throw new InvalidOperationException("You'll need to call this method later in the pipeline - the session state mechanism (SessionStateModule, which is an IHttpModule) has not been initialised yet. Try calling this method in Global.asax's Application_BeginRequest. Also make sure that you do not specify a partitionResolverType in your web.config."); 

     //ensure that the session has not been used prior to this with an incorrect app ID 
     var isStaticSqlPartitionInfoInitialised = GetFieldValue<bool>(sqlSessionStatePartitionInfoInstance, "_sqlInfoInited"); 
     if (isStaticSqlPartitionInfoInitialised) 
      throw new InvalidOperationException("You'll need to call this method earlier in the pipeline - before any sessions have been loaded."); 

     //force initialisation of the static SqlSessionStateStore.SqlPartitionInfo instance - otherwise this will happen later and overwrite our change 
     var connectionString = GetFieldValue<string>(sqlSessionStatePartitionInfoInstance, "_sqlConnectionString"); 
     using (var connection = new SqlConnection(connectionString)) 
     { 
      connection.Open(); 
      CallInstanceMethod(sqlSessionStatePartitionInfoInstance, "InitSqlInfo", connection); 
     } 

     //calculate and set the application hash code 
     string applicationNameHashCode = GetHashCode(appName).ToString("x8", CultureInfo.InvariantCulture); 
     GetField(sqlSessionStatePartitionInfoInstance, "_appSuffix").SetValue(sqlSessionStatePartitionInfoInstance, applicationNameHashCode); 
    } 

    static int GetHashCode(string appName) 
    { 
     string s = appName.ToLower(); 
     int hash = 5381; 
     int len = s.Length; 

     for (int i = 0; i < len; i++) 
     { 
      int c = Convert.ToInt32(s[i]); 
      hash = ((hash << 5) + hash)^c; 
     } 

     return hash; 
    } 

    static void CallInstanceMethod(object instance, string methodName, params object[] parameters) 
    { 
     var methodInfo = instance.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance); 
     methodInfo.Invoke(instance, parameters); 
    } 

    static object GetStaticFieldValue(Type typeWithStaticField, string staticFieldName) 
    { 
     return typeWithStaticField.GetField(staticFieldName, BindingFlags.NonPublic | BindingFlags.Static).GetValue(null); 
    } 

    static FieldInfo GetField(object instance, string name) 
    { 
     return instance.GetType().GetField(name, BindingFlags.NonPublic | BindingFlags.Instance); 
    } 

    static T GetFieldValue<T>(object instance, string name) 
    { 
     return (T)GetField(instance, name).GetValue(instance); 
    } 
} 
+0

每個** web應用程序**都有自己的*** appName ***? – Kiquenet

相關問題