Un HttpModule per rendere trasparente l'utilizzo di Session di ASP.NET su StateServer out of process

di Marco De Sanctis, in ASP.NET, ASP.NET 2.0, ASP.NET 3.5,

ASP.NET mette a disposizione due provider aggiuntivi per la persistenza della sessione, uno basato su SQL Server e uno su un servizio Windows, che in teoria potrebbero essere facilmente sostituiti a quello InProcess, di default, semplicemente agendo su web.config:

<sessionState mode="StateServer" timeout="180" 
stateConnectionString="tcpip=localhost:42424" />

In realtà l'operazione nasconde una pericolosa insidia, ma per capirla prima soffermiamoci su come lavorano tali oggetti.

Un provider out of process, prima che l'HttpHandler associato alla richiesta venga eseguito, e più precisamente in corrispondenza dell'evento AcquireRequestState, richiede allo State Server la sessione sotto forma di uno stream di byte, che viene deserializzata e resa disponibile all'handler stesso (che, nel 99% dei casi, sarà una pagina web ASP.NET). Successivamente, in ReleaseRequestState, la sessione viene di nuovo serializzata ed inviata al server.

Proprio in questa fase sorgono i problemi: la serializzazione della session, infatti, funziona più o meno secondo questo schema:
- cicla per tutte le chiavi;
- per ogni chiave, serializza il contenuto.

Questo vuol dire che se due chiavi condividono dei riferimenti, dopo un PostBack questi verranno irrimediabilmente persi! Ad esempio, se effettuiamo un test in una pagina con il codice seguente,

if (!IsPostBack)
{
  Session["A"] = new object();
  Session["B"] = Session["A"];
}

Debug.Assert(Session["A"] == Session ["B"]);

possiamo verificare che l'assert, in caso di sessione OutOfProcess, fallisce dopo il primo PostBack.

Ora, una situazione di questo tipo è potenzialmente parecchio pericolosa, perché può causare bug piuttosto difficili da individuare ed è un qualcosa di "innaturale" rispetto al modello a oggetti che il developer è abituato ad utilizzare interagendo con la session in memory. E' piuttosto facile commettere errori di distrazione che conducano a malfunzionamenti e passare allo state server a progetto in corso o (peggio ancora) ultimato, richiede una completa e assolutamente non banale revisione del codice.
Come risolvere? Il problema è che il dictionary della sessione viene serializzato nodo per nodo e non nel suo insieme; se l'intero contenuto della sessione fosse però in un dictionary a parte, referenziato in un singolo nodo della session, questo verrebbe serializzato come un tutt'uno e i riferimenti verrebbero preservati.

Se allora si vuol rendere il tutto veramente trasparente, bisogna creare un HttpModule che faccia proprio questo tipo di lavoro. Al termine dell'esecuzione della pagina, ma prima della serializzazione della Session, viene scatenato l'evento PostRequestHandlerExecute, in corrispondenza del quale si crea il dictionary e vi si salva il contenuto della sessione:

private void context_PostRequestHandlerExecute(object sender, EventArgs e)
{
  HttpContext context = HttpContext.Current;
  if (context != null && context.Session != null)
  {
    Dictionary<string, object> dictionary = new Dictionary<string, object>();
    foreach (string sessionKey in context.Session.Keys)
    {
      dictionary.Add(sessionKey, context.Session[sessionKey]);
    }
    context.Session.Clear();
    context.Session.Add("dictionary", dictionary);
  }
}

Successivamente ad una nuova richiesta, la fase di deserializzazione restituirà una session con una sola chiave contenente il dictionary stesso. A questo punto è necessario esploderlo e ripristinare la session originale, e per far ciò basta gestire PostAcquireRequestState, che avviene comunque prima dell'esecuzione dell'handler e quindi assicura la più totale trasparenza per il nostro codice in pagina:

private void context_PostAcquireRequestState(object sender, EventArgs e)
{
  HttpContext context = HttpContext.Current;
  if (context != null && context.Session != null)
  {
    Dictionary<string, object> dictionary =
        context.Session[key] as Dictionary<string, object>;
    if (dictionary != null && dictionary.Keys.Count > 0)
    {
      foreach (KeyValuePair<string, object> pair in dictionary)
      {
        context.Session[pair.Key] = pair.Value;
       }
     }
     context.Session[key] = null;
  }
}

Per attivare questo HttpModule è sufficiente registrarlo nel web.config, avendo cura però di posizionarlo prima di ogni altro HttpModule dichiarato; ciò serve a far sì che venga eseguito prima di ogni altro modulo, garantendo il corretto funzionamento della sessione anche nel caso in cui qualcuno di essi dovesse farne uso.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi