8

Sto configurando una demo molto semplice dello stato sessione SQL Server, ma sto avendo qualche problema a farlo funzionare. Sto provando a testare localmente su Windows 7 con IIS 7.5 e SQL Server 2008 R2.Condivisione dello stato sessione SQL Server attraverso applicazioni Web

In definitiva, ho bisogno di un modo per tenere traccia del numero di utenti registrati in un sistema che è bilanciato dal carico tra alcuni server web diversi. Quindi ho bisogno di aggiornare una variabile di sessione (memorizzata in SQL) ogni volta che un utente accede o esce. Quindi l'ID di sessione potrebbe sempre essere diverso. È possibile?

Ecco che cosa ho fatto finora:

  1. creato due nuovi siti in IIS (Site1 e Site2)
  2. creato due nuovi progetti di applicazioni Web in VS 2010 (Site1 e Site2)
  3. In entrambi i siti, nella pagina Default.aspx, ho creato un pulsante che quando si fa clic salverà "Site1" o "Site2" (a seconda del sito) in una variabile di sessione chiamata "LastSiteUsed". C'è un altro pulsante, che quando viene cliccato, leggerà il valore dalla variabile di sessione "LastSiteUsed" e lo visualizzerà in un'etichetta.
  4. In SQL, ho creato un nuovo database chiamato dbSessionTest. Quindi ho seguito le istruzioni su MSDN to install the Session State Database using the aspnet_regsql tool.
  5. In entrambi mia Web file web.config dell'applicazione, ho aggiunto il seguente punto <System.Web>:

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

Entrambi i siti funzionano normalmente, ma non sembrano essere condivisione la sessione. Ad esempio, quando faccio clic sul mio pulsante per salvare la mia variabile di sessione da Site1, mi aspetto di poter leggere quel valore da Site2, ma non funziona. Site2 restituirà "Site2" e Site1 restituirà "Site1".

Qualcuno ha qualche idea su cosa potrei fare male? Mi sbaglio nel pensare che dovrei essere in grado di leggere un valore impostato da Site1 da Site2?

UPDATE:

posso vedere che i dati della sessione è sempre memorizzato nella tabella ASPStateTempSessions in SQL Management Studio, ma ogni sito può ancora vedere solo il valore che ha scritto. Entrambi i siti sono la creazione della variabile di sessione in questo modo:

Session("LastSiteUsed") = "Site2" 

E entrambi i siti stanno recuperando il valore come:

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

Ho bisogno di fare questo in modo diverso per l'accesso a variabili di sessione memorizzati in SQL Server?

UPDATE 2:

Ho provato con il database predefinito, ASPState, che viene creato eseguendo questo comando:

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

Poi semplificato ogni mio Web.file di configurazione come:

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

Ma ancora, nessuna fortuna. Voglio essere in grado di impostare una variabile di sessione da Site1 e quindi leggere il valore da Site2, ma non funziona. Ancora una volta, sono in grado di vedere le voci presenti nella tabella ASPStateTempSessions, quindi so che verranno inserite correttamente. Una cosa che ho notato è che hanno ID di sessione diversi?

C'è qualcosa che devo fare in modo diverso per garantire che venga utilizzato lo stesso ID di sessione tra i miei siti quando si imposta/legge la stessa variabile di sessione?

UPDATE 3:

Ho seguito le istruzioni da this article che modifica una SP e aggiunge una colonna per raggruppare al tavolo ASPStateTempApplications. Questo tipo di funzionava, ma solo se entrambi i miei siti erano aperti nello stesso browser (utilizzando le schede). Devo essere in grado di aprire Site1 in IE, salvare un valore nella mia variabile di sessione, chiudere il browser, aprire Site2 in Chrome e leggere il valore. Da tutto ciò che ho letto con SQL Server Session State ... dovrebbe essere possibile.

UPDATE 4 - SOLUZIONE:

ho seguito la risposta su this article fornito da @SilverNinja. Ho ricreato il database ASPState tramite il prompt dei comandi (per annullare le mie modifiche SP nell'aggiornamento n. 3), quindi modificato lo SP TempGetAppID secondo la risposta dal collegamento. Ho anche aggiornato entrambi i miei file Web.config per abbinare la risposta da tale articolo così:

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

ho incluso anche identici chiavi della macchina in entrambi i file Web.config:

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

Ora sono in grado di (utilizzando IE) di aprire entrambi i miei siti, impostare un valore da Site1 e leggerlo da Site2 (e viceversa). Quando controllo la tabella, esiste un solo SessionID (entrambi i siti utilizzano correttamente la stessa sessione). Ho sbagliato a pensare che aprire un nuovo browser (ad esempio, con Chrome), avrebbe utilizzato la stessa sessione: un nuovo browser inizierà la propria sessione. Tuttavia, in uno scenario con bilanciamento del carico, questo non dovrebbe causare problemi.

+0

'Nome applicazione = CacheTest' è uguale per tutte le applicazioni Web (WebSites)? o ** valori diversi **? – Kiquenet

risposta

7

Il problema riscontrato è stato di condivisione della condivisione tra le applicazioni ASP.NET. Questo non è supportato immediatamente con il provider di sessioni di SQL Server. Vedi questo SO post regarding what changes to make to SQL Server session provider to support cross-application sessions.

Non utilizzare la sessione per la gestione di questo stato condiviso (ad esempio LastSiteUsed). Questa informazione deve essere mantenuta con il negozio profilo utente (Membership, Profile provider, DB personalizzato, ecc.). Poiché la sessione può scadere, l'utilizzo di una sessione condivisa tra applicazioni non è un meccanismo affidabile per tenere traccia dello stato specifico dell'utente persistente. Se la persistenza non è critica di utilizzare lo AppFabric Cache per uno stato condiviso tra applicazioni in memoria.

+0

Grazie per la risposta! Penso che dovrebbe andare bene se la sessione scade (se sto capendo correttamente) perché in definitiva sto solo rintracciando il numero di utenti simultanei loggati. Detto questo, il tuo primo collegamento sarebbe sufficiente per realizzare ciò di cui ho bisogno (in particolare la risposta da quel collegamento)? – lhan

+0

FWIW Sono andato avanti e ho provato questa soluzione, ma non ha funzionato. Pensandoci un po 'di più, questa situazione è diversa. Volevano condividere una sessione sullo stesso server. Devo condividere una variabile di sessione tra più server con bilanciamento del carico, archiviati in SQL. Sembra che dovrebbe essere possibile? – lhan

+0

Se i server sono bilanciati, è necessario assicurarsi che tutti abbiano la ** stessa chiave macchina ** nel proprio 'web.config' o' machine.config' per le applicazioni che accedono alla sessione condivisa. Che si tratti dello stesso server o di server diversi, non importa se le chiavi sono le stesse. – SliverNinja

2

Per chi vuole o ha bisogno di risolvere questo problema senza modificare le stored procedure del database, considerare questo approccio (non per i deboli di cuore).

L'idea di base è che sia presente un'istanza statica di SqlSessionStateStore.SqlPartitionInfo memorizzata in System.Web.SessionState.SqlSessionStateStore.s_singlePartitionInfo da inizializzare chiamando InitSqlInfo. Una volta inizializzata questa istanza, imposto il valore del campo interno _appSuffix. L'istanza deve essere inizializzata prima di impostare il campo _appSuffix, altrimenti il ​​mio valore personalizzato verrà sovrascritto durante l'inizializzazione. Calcolo il codice hash dal nome dell'applicazione fornito utilizzando la stessa funzione di hash come nel database di stato ASP.NET.

Si noti che questo è tanto brutto quanto diventa e dipende completamente dai dettagli di implementazione interni che possono cambiare in qualsiasi momento. Tuttavia, non conosco alcuna alternativa pratica. Utilizzare a propria discrezione e rischio.

Ora che ho fornito la mia dichiarazione di non responsabilità, sarei sorpreso se questa implementazione in .NET BCL fosse cambiata poiché questo approccio all'autenticazione è stato sostituito e non vedo alcun motivo per cui Microsoft possa armeggiare lì.

/* 
* 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

Ogni ** applicazione web ** ha il proprio *** appName ***? – Kiquenet

Problemi correlati