Sulla Programmazione

Quattro chiacchere sulla programmazione e sulle bit-tecnologie con Fabrizio Cipriani

Scambio di dati tra pagine ASP e ASP.NET

Quando ho iniziato il porting di un grande progetto da ASP a ASP.NET, mi sono trovato di fronte ad un problema fondamentale: come posso evitare di bloccare del tutto lo sviluppo del codice originale durante il porting, quando questo ancora viene modificato giornalmente?

Ho dovuto quindi adottare un approccio graduale, trovandomi inevitabilmente in uno stato in cui parte dell'applicazione gira in .net, parte in ASP classico.

Una volta scelto come procedere, sono passato ad affrontare il problema, questa volta tecnico, riguardante la condivisione delle variabili di sessione tra i due ambienti.

ASP Classico e .NET girano infatti in due spazi di memoria differenti e nessun è in grado di vedere le variabili di sessione dell'altro.

Da una ricerca su internet, sono venute fuori varie soluzioni:

  1. Condividere i dati im tabelle di un database SQL
  2. Passare i dati da una pagina di una applicazione all'altra attraverso un POST
  3. Usare la classe .NET WebRequest per chiamare una pagina ASP che stampasse i valori di tutte le variabili di sessione ed elaborare il risultato.

Ero particolarmente affascinato dalla terza soluzione, ma si presentava il problema di come invocare la pagina con il cookie di sessione corretto.

Iniziamo dal principio:

  • La sessione ASP viene creata dal server IIS la prima volta che un particolare utente naviga sul sito.
  • Il server genera un cookie (chiamato appnto cookie di sessione) che viene memorizzato nel browser.
  • Questo fa in modo che il cookie venga inviato dal browser in ogni nuova pagina che viene aperta sul sito, permettendo così al server di recuperare i dati di sessione e di renderli disponibili all'applicazione.

Il nome di un cookie di sessione ASP inizia sempre con "ASPSESSION", segue poi un valore generato casualmente.

Se l'utente apre una pagina della nostra nuova applicazione .NET, possiamo scorrere la lista dei cookie contenuti nella collection Cookies dell'oggetto Request, identificare quello che inizia con "ASPSESSION" ed usarlo per invocare una pagina dell'applicazione ASP che stampa tutti i valori delle sue variabili di sessione:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class ASPSessionData :
        System.Collections.Specialized.NameObjectCollectionBase  
{   
  private HttpContext _context;  
  private String _ASPUrl;  
  private String _ASPCookieName;  
  private String _ASPCookieValue;
 
  public ASPSessionData(HttpContext context)  
  {  
    _context = context;  
    _ASPUrl = "http://nomesito/ASPSession.asp";  
    _ASPCookieName = "";  
    _ASPCookieValue = "";  
  }

  public Object this[String key]  
  {  
    get  
    {  
       return (this.BaseGet(key));  
    }

    set  
    {  
       this.BaseSet(key, value);  
    }  
  }

  public String[] AllKeys  
  {  
    get  
    {  
      return (this.BaseGetAllKeys());  
    }  
  }

  public void Clear()  
  {  
    this.BaseClear();  
  }  
}

Facciamo derivare la classe da NameObjectCollectionBase, così da avere a disposizione le stesse funzionalità delle classi HttpSessionState e HttpApplicationState. Nel costruttore inizializziamo la variabile _ASPUrl con la url della pagina ASP nella quale metteremo la stampa dei valori di sessione, e _context nel quale memorizziamo il contesto corrente che useremo per ricercare il nome del cookie usato per gestire la sessione ASP.

Aggiungiamo inoltre il metodo Clear() per svuotare la collection, mentre la proprietà AllKeys ci servirà per recuperare velocemente tutte le keys per ciclare su tutti i valori della collection.

Ora iniziamo a scrivere i metodi necessari alla cattura delle informazioni in ASP. Il primo metodo serve a trovare il nome ed il valore del cookie ASP. Per questo semplicemente scorriamo la collection Cookies della pagina corrente fino a quando non troviamo un cookie che inizia per "ASPSESSION". Una volta trovato, lo memorizziamo in _ASPCookieName e _ASPCookieValue:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
private bool GetSessionCookie()  
{  
  String[] CookieArray =   
    _context.Request.Cookies.AllKeys;

  HttpCookie mycookie;

  for ( int i = 0; i < CookieArray.Length; i++ )  
  {  
    mycookie =   
      _context.Request.Cookies[CookieArray[i]];

    if ( mycookie.Name.StartsWith("ASPSESSION"))  
    {  
     _ASPCookieName = mycookie.Name;  
     _ASPCookieValue = mycookie.Value;  
     return true;  
    }  
  }

  return false;  
}

Nel metodo successivo creiamo la WebRequest, aggiungendo nell'header il valore del cookie, per permettere al server che esegue il file ASP di caricare i dati di sessione: 

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
private HttpWebRequest CreateRequest()  
{  
  HttpWebRequest myRequest =  
    (HttpWebRequest)WebRequest.Create(_ASPUrl);

  myRequest.Headers.Add  
    ("Cookie: " + _ASPCookieName + "=" + _ASPCookieValue);

  return myRequest;  
}

Nel prossimo metodo invece catturiamo l'output del file ASP usando GetResponse() e GetResponseStream().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private String CaptureResponse( HttpWebRequest request)  
{  
  if (String.IsNullOrEmpty(_ASPCookieName) ||  
    String.IsNullOrEmpty(_ASPCookieValue))  
  {
      return ""; 
  }

  HttpWebResponse myResponse =  
     (HttpWebResponse)request.GetResponse();

  Stream responseStream =  
     myResponse.GetResponseStream();

  StreamReader sr =  
     new StreamReader(responseStream);

  string ASPContent = sr.ReadToEnd();

  sr.Close();  
  myResponse.Close();

  return ASPContent;  
}

Infine eseguiamo un parse della cattura. Come vedremo in seguito, nel file ASP ho eseguito le Response.Write() delle variabili di sessione usando questo formato: [nome variabile sessione]=[valore variabile][a capo]. Il metodo ParseAspContent() suddivide l'output prima in linee differenti e poi in left e right values, memorizzando i valori nella collection interna della classe:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Public void ParseASPContent(String aspcontent)  
{  
  if (String.IsNullOrEmpty(aspcontent))  
    return;

  String[] vars = aspcontent.Split(new String[] { "\\r\\n" },
    StringSplitOptions.RemoveEmptyEntries);

  for (int i = 0; i < vars.Length; i++)  
  {  
    String[] leftright = vars[i].Split(new char[] { '=' });

    if (leftright.Length > 1)  
      this.BaseAdd(leftright[0], leftright[1]);  
    else  
      this.BaseAdd(leftright[0], "");  
  }  
}

Aggiungiamo ora il tocco finale: un metodo che invia i valori desiderati da .NET a ASP, facendo il tragitto inverso, in modo da poter sincronizzare le variabili di sessione di entrambi gli ambienti in un singolo passaggio.

L'invio viene effettuato usando un POST, invocando la stessa pagina ASP che usiamo per leggere i valori di sessione. Come vedremo, quella pagina conterrà, oltre al codice che stampa le variabili di sessione, un ramo di codice che accetta il POST, legge i valori passati, e li salva in variabili di sessione:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void SendPostData(HttpWebRequest request)  
{  
  String postdata = "";

  for ( int i=0; i<this.Count; i++)  
  {  
    postdata += ((postdata.Length \> 0) ? "&" : "") +  
      this.BaseGetKey(i) + "=" + this.BaseGet(i);  
  }

  ASCIIEncoding encoding = new ASCIIEncoding();

  byte[] data = encoding.GetBytes(postdata);

  request.Method = "POST";  
  request.ContentType = "application/x-www-form-urlencoded";  
  request.ContentLength = data.Length;

  Stream stream = request.GetRequestStream();
  stream.Write(data, 0, data.Length);  
  stream.Close();  
}

I dati del POST vengono costruiti iterando sui valori della collection interna alla classe. Questi vengono poi inviati al file ASP usando GetRequestStream() per prendere lo stream sul quale scrivere i dati verso la Request. Possiamo ora mettere tutto assieme:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public void GetASPSessionData(bool sendPostData)  
{  
  _context = context;  
  _ASPUrl = "http://nomesito/ASPSession.asp";  
  _ASPCookieName = "";  
  _ASPCookieValue = "";

  // Prende nome e valore dei cookie  
  GetSessionCookie();

  // Crea la request  
  HttpWebRequest myRequest = CreateRequest();

  // Invia i dati in post se richiesto  
  if ( sendPostData )  
    SendPostData(myRequest);

   // Cattura le response  
   String myASPContent = CaptureResponse(myRequest);

   // Esegue il parse delle variabili  
   ParseASPContent(myASPContent);  
}

Il file ASP "ASPSession.asp" non deve fare altro che stampare le sue variabili sessione ( io in questo caso ho usato dei semplici Response.Write() ).

Inoltre, se riceve dei valori attraverso un POST, memorizza i dati inviati all'interno della propria sessione ASP.

Notate anche il controllo effettuato sull'indirizzo del chiamante, per evitare che un agente esterno possa infrangere la sicurezza del vostro sito leggendo i dati di sessione:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
if Request.ServerVariables("REMOTE_ADDR") = Request.ServerVariables("LOCAL_ADDR") then

  ' Stampa i dati sessione ASP in modo che possano essere  
  ' catturati dal metodo CaptureResponse()  
  For each s in Session.Contents   
    Response.Write s & "=" & Session.Contents(s) & vbcrlf  
  Next

  ' Se arrivano dei dati da ASP.NET, li memorizza in variabili  
  ' sessione ASP  
  For each f in Request.Form  
    Session( f ) = Request.Form(f)  
  Next

End If

Infine ecco codice di esempio da inserire in una pagina ASP.NET per leggere le variabili di sessione ASP:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
ASPSessionData aspSession = new ASPSessionData(Context);  
aspSession.GetASPSessionData(false);

Response.Write("Valori variabili di sessione ricevuti da ASP:<br>");
foreach (String s in aspSession.AllKeys)  
  Response.Write(s + " = " + aspSession[s] + "<br>");

aspSession.Clear();

aspSession["ASP.NETValue1"] = "Passo il primo valore da .NET";  
aspSession["ASP.NETValue2"] = "Ed il secondo valore .NET";

aspSession.GetASPSessionData(true);

Response.Write("Valori variabili di sessione .NET passati ad ASP<br>");
foreach (String s in aspSession.AllKeys)  
  Response.Write( s + " = " + aspSession[s] + "<br>");

La classe si può migliore automatizzando maggiormente il passaggio delle variabili di sessione .NET ad ASP, scrivendo metodi che scambiano singole variabili di sessione piuttosto che il pacchetto completo, e aggiungendo la gestione degli errori. Naturalmente non è in grado di scambiare dati serializzati o di tipo binario, ma già così dovrebbe risolvere molti dei problemi di convivenza tra vecchie e nuove applicazioni.

Per finire, questi sono i link ad alcuni metodi alternativi di condividere le variabili di sessione in ASP:

http://msdn2.microsoft.com/en-us/library/aa479313.aspx

Spiega come serializzare i dati di sessione in una tabella SQL attraverso un assembly .net, in modo da usarlo per scambiare i dati tra i due ambienti. Quella di Billy Yuen è la soluzione fornita ufficialmente da Microsoft, ma personalmente l'ho trovata un pò cervellotica e ho trovato messaggi di utenti che affermavano di non riuscire a farla funzionare.

http://www.devx.com/webdev/Article/30811

Spiega come crittografare i dati di sessione ed inviarli con un POST avanti ed indietro tra ASP e ASP.NET. La soluzione di Bryan Roberts pare migliore, ma l'obbligo di definire un form ogni volta che si effettua un link, per quanto si possa automatizzare, diventa una operazione difficile da gestire in una applicazione complessa.

http://searchwindevelopment.techtarget.com/tip/0,289483,sid8_gci951935,00.html

Questo è il post che ho usato come spunto per il codice che ho incluso sopra.

Comments