Response.Redirect e Server.Transfer

Se ritieni utile questo articolo, considera la possibilità di effettuare una donazione (il cui importo è a tua completa discrezione) tramite PayPal. Grazie.

Introduzione

Nella realizzazione di applicazioni web risulta spesso necessario consentire all'applicazione di spostarsi tra le diverse pagine che la compongono, oppure di inoltrare richieste a documenti esterni all'applicazione stessa.

Mentre la navigazione delle pagine è un aspetto ovvio dal punto di vista dell'utente - e si realizza fondamentalmente utilizzando link (tag <a>) HTML e i submit delle form (ed implicitamente grazie ai server controls di ASP.NET) - per consentire di effettuare una sorta di navigazione anche dal lato dell'applicazione sono necessari dei comandi che agiscano lato server, e che in modo trasparente all'utente facciano sì che sia il server a decidere che pagina il client dovrà visualizzare.

Il Framework .NET presenta due alternative per navigare da una pagina web all'altra utilizzando codice lato server, entrambe senza dubbio conosciute, ma che in molti casi vengono utilizzate senza sapere come effettivamente agiscono, cosa che in certe situazioni può venire utile ed evitare spiacevoli sorprese.

Queste due alternative sono i metodi Redirect e Transfer, appartenenti rispettivamente alle classi HttpResponse e HttpServerUtility del namespace System.Web, e che all'interno di una pagina .aspx sono accessibili tramite gli oggetti Response e Server, da cui l'abitudine di riferirsi ad essi come Response.Redirect e Server.Transfer.

HttpResponse.Redirect

Un primo sguardo

Questo metodo consente di imporre al client (browser) di richiedere la pagina indicata nel suo parametro di ingresso.

L'effetto visibile all'utente è che il browser visualizzerà una pagina diversa da quella che in origine era stata richiesta.

Response.Redirect("nuovapagina.aspx");

Quello che accade dietro le quinte è l'invio al client, da parte del server, di una risposta HTTP il cui header contiene due differenze rispetto alla risposta che sarebbe stata generata da quella pagina se il metodo non fosse stato invocato:

  • il codice HTTP della risposta è 302 Found, che significa che la pagina cercata è stata temporaneamente spostata;

  • all'header è appesa la direttiva Location, a fianco della quale è specificato il nuovo indirizzo della pagina.

HTTP/1.1 302 Found
Server: Microsoft-IIS/5.1
Location: /nuovadirectory/nuovapagina.aspx

Queste due direttive, assieme, forzano il browser ad eseguire una nuova richiesta, di tipo GET, alla nuova risorsa specificata.

È chiaro da questa breve descrizione che il metodo Redirect si appoggia ad un meccanismo del protocollo HTTP che servirebbe ad altri scopi, facendo credere al browser che la pagina richiesta sia stata spostata, inducendolo quindi a richiedere la nuova pagina specificata nella direttiva Location.

Approfondimento

Finora abbiamo visto cosa succede dietro le quinte e qual'è l'effetto visibile all'utente, ma cosa accade alla pagina che era stata richiesta originariamente?

Per chiarire questo concetto è necessario sapere che il metodo Redirect presenta un overload, che oltre ad accettare un parametro di tipo String ne accetta un altro di tipo Boolean.

Nome Descrizione
HttpResponse.Redirect (String) Redirige il client al nuovo Url specificato e termina l'esecuzione della pagina corrente.
HttpResponse.Redirect (String, Boolean) Redirige il client al nuovo Url e specifica se l'esecuzione della pagina corrente deve terminare.

Naturalmente chiamare Redirect(String) equivale a chiamare il secondo overload con il parametro Boolean posto a true. In questo caso l'esecuzione della pagina termina.

Ma cosa significa che l'esecuzione della pagina corrente termina?

Beh, significa proprio letteralmente quello. Quando nel code-behind o inline della pagina .aspx viene invocato il metodo Redirect(String) o Redirect(String, Boolean) con il parametro Boolean posto a true l'esecuzione del codice si interrompe bruscamente.
Più precisamente, viene chiamato il metodo End dell'oggetto HttpResponse, il quale invia tutto l'output presente nel buffer della risposta al client, interrompe l'esecuzione del codice e scatena l'evento EndRequest, che può essere gestito nel file global.asax.

Di conseguenza la pagina che viene inviata al client (per capirci, quella che contiene le informazioni per il redirect e l'header col codice 302) contiene solo e soltanto il contenuto che fino a quel momento era stato generato, oltre a dell'HTML di default generato da IIS, che contiene un link alla nuova pagina, il quale viene generato probabilmente per compatibilità, nel caso in cui il browser di destinazione non supporti (attualmente tutti lo supportano) il redirect automatico tramite le direttive dell'header, e quindi consentendo all'utente di seguire il redirect manualmente cliccando sul link:

HTTP/1.1 302 Found
Server: Microsoft-IIS/5.1
Location: /nuovadirectory/nuovapagina.aspx

<html>
<head>
<title>Object moved</title>
</head>
<body>
<h2>Object moved to <a href='/nuovadirectory/nuovapagina.aspx'>here</a>.</h2>
</body>
</html>

Nel caso in cui, invece, si chiami il metodo Redirect con il secondo parametro posto a false l'esecuzione della pagina non viene terminata ma procede fino alla sua conclusione, e solo in quel momento viene inviata al client la risposta. La risposta, quindi, oltre a contenere le informazioni di redirect, contiene anche tutto l'html generato dalla pagina precedente:

HTTP/1.1 302 Found
Server: Microsoft-IIS/5.1
Location: /nuovadirectory/nuovapagina.aspx

<html>
<head>
<title>Object moved</title>
</head>
<body>
<h2>Object moved to <a href='/nuovadirectory/nuovapagina.aspx'>here</a>.</h2>
</body>
</html>

<HTML>
<HEAD>
<title>Prima pagina</title>
<meta name="GENERATOR" Content="Microsoft Visual Studio .NET 7.1">
<meta name="CODE_LANGUAGE" Content="C#">
<meta name=vs_defaultClientScript content="JavaScript">
<meta name=vs_targetSchema content="http://schemas.microsoft.com/intellisense/ie5">
</HEAD>
<body>
<form name="Form1" method="post" action="primapagina.aspx" id="Form1">
<input type="hidden" name="__VIEWSTATE" value="dDwtMjAwODAxNTcwNTs7PnF0SiGobsHq+sJep5ADL4nmGZ1H" />
[... contenuto della pagina ...]
</form> </body> </HTML>

Poiché il contenuto di questa pagina non viene mostrato all'utente, visto che la redirezione avviene immediatamente, sembrerebbe inutile la possibilità di specificare se terminare o meno l'esecuzione della pagina. In realtà, oltre ad essere comunque necessario in alcune situazioni, consentire alla pagina di terminare la sua esecuzione serve anche ad evitare un problema insito nell'implementazione nel metodo e documentato in questo articolo del supporto tecnico Microsoft.

In parole povere l'interruzione dell'esecuzione della pagina solleva un eccezione di tipo ThreadAbortException.

Differenza di prestazioni tra i due overload del metodo

Il fatto di vedere sollevare un'eccezione può insospettire qualcuno, poiché sollevare delle eccezioni non è generalmente una buona pratica dal punto di vista delle prestazioni. Tuttavia la scelta deve essere valutata rispetto all'altra alternativa.

In altre parole, dal punto di vista prestazionale è meglio terminare l'esecuzione della pagina (e quindi scatenare l'eccezione) oppure farla continuare sul thread corrente?

La risposta non è univoca, perchè dipende dalla pagina in esame. Più semplicemente, poiché come detto in precedenza nel caso di non interruzione dell'esecuzione la pagina tutto il suo ciclo vitale viene eseguito, e solo successivamente viene inviata al client con le informazioni di redirect, è necessario valutare se andrà ad influire sulle prestazioni più pesantemente la generazione di un'eccezione oppure l'elaborazione di una pagina - compreso tutto il suo markup - non necessaria, in quanto non verrà mai visualizzata.

Il team di ASP.NET ha evidentemente preferito lasciare che venga sollevata l'eccezione, poiché la chiamata al metodo Redirect senza il parametro Boolean lo imposta di default a true.

Proprietà della pagina oggetto del redirect e passaggio di dati

Indipendentemente dall'overload del metodo utilizzato, una volta reindirizzati alla nuova pagina le differenze tra i due svaniscono, ed il risultato è quello di avere una pagina fresca, completamente indipendente dalla pagina dalla quale si è partiti e nella barra degli indirizzi del browser l'url della nuova pagina.

In questo modo, quindi, non sembrerebbe possibile passare parametri da una pagina all'altra utilizzano il metodo Redirect. In realtà il passaggio di parametri è possibile se effettuato in modo esplicito tramite QueryString, semplicemente chiamando il metodo appendendo le coppie nome/valore all'url della pagina in modo programmatico. In questo modo la pagina di destinazione potrà accedere a questi valori accedendo alla proprietà QueryString dell'oggetto Request.

Il codice seguente mostra un esempio di redirect utilizzando un parametro di QueryString:

Response.Redirect("nuovapagina.aspx?nome=valore");

Nota: è consigliabile codificare tutti i valori passati da QueryString utilizzando il metodo UrlEncode della classe HttpUtility.

Questo è invece il codice necessario per recuperare i valori dalla pagina di destinazione:

string val = Request.QueryString["nome"];

Nota: è consigliabile operare sui valori recuperati da QueryString utilizzando il metodo UrlDecode della classe HttpUtility.

Un'ultima caratteristica non trascurabile relativa alla pagina che è possibile raggiungere tramite il metodo Redirect è la possibilità di aprire una pagina esterna all'applicazione, poiché, come visto, la chiamata del metodo in sostanza si limita ad un redirect effettuato del browser, il quale potrà quindi richiedere una qualsiasi pagina, sia essa un documento dinamico o statico.

Osservazioni sulle prestazioni di rete

Il metodo Redirect è molto utile per spostarsi tra le pagine, ma è in certi casi non consigliabile, ed addirittura contestato, per la necessità di due roundtrip da/verso il server.

Cosa significa questo? Un roundtrip è un "giro" completo del percorso di andata e ritorno tra il client ed il server.

Intuibilmente minore è il numero di roundtrip effettuati migliori sono le prestazioni della rete, la velocità della risposta, ed il carico del server.

L'utilizzo del metodo Redirect è molto oneroso da questo punto di vista, perchè richiede due roundtrip. Il primo avviene durante la richiesta della prima pagina, che a sua volta tornerà al browser una pagina (mai visualizzata) contenente le informazioni di redirect. Il secondo avviene quando il browser, in modo trasparente all'utente, richiede la pagina specificata nelle informazioni di redirect ed il server gliela invia.
Il risultato visibile è quindi che si ottiene come risposta una pagina, ma con due roundtrip. Evidentemente non ottimale!

Il metodo descritto di seguito, pur creando visivamente un effetto simile a quello del metodo Redirect, presenta notevoli differenze.

HttpServerUtility.Transfer

Un primo sguardo

Il metodo Transfer della classe HttpServerUtility serve per fare uno switch dall'esecuzione di una pagina ad un'altra senza passare dal client, ed effettuando tutte le operazioni lato server.

Il risultato che si ottiene è che il server riceve la richiesta per la pagina, processa la richiesta ma quando trova l'invocazione del metodo interrompe l'esecuzione del codice della pagina ed inizia quello della pagina specificata nel parametro di input del metodo. Al client viene quindi ritornata la nuova pagina senza che si sia accorto di niente.

Questo appare evidente anche dal fatto che nella barra degli indirizzi del browser compare sempre l'indirizzo della prima pagina. Questo perchè il client non è a conoscenza dell'operazione eseguita dal server, e crede che la pagina ottenuta come risposta sia esattamente quella richiesta.

In altre parole, in questo caso non ci sono "trucchetti" del server per forzare il browser a richiedere una nuova pagina, ma è il server stesso che gliela invia.

Approfondimento

Dopo questa breve descrizione si possono già intuire alcune delle differenze che caratterizzano il metodo Transfer rispetto a Redirect:

  1. poiché è il server ad eseguire l'elaborazione della nuova pagina, il parametro passato al metodo può essere solo l'url di una pagina presente sul server, altrimenti esso non è ovviamente in grado di elaborarla;

  2. il parametro deve fare riferimento ad una pagina .aspx, non è possibile richiedere un altro tipo di risorsa;

  3. l'esecuzione della pagina corrente termina inevitabilmente, poiché la risposta sarà unica e sarà quella generata dalla pagina verso la quale il trasferimento viene effettuato. Di conseguenza anche in questo caso viene sollevata un'eccezione di tipo ThreadAbortException, da cui non è possibile svincolarsi.

Oltre a queste differenze abbastanza intuibili, ne esistono altre, che rappresentano del resto i vantaggi che questo metodo ha nei confronti di quello descritto in precedenza.

Proprietà della pagina oggetto del trasferimento e passaggio di parametri

Come detto, in questo caso il trasferimento da una pagina ad un'altra avviene completamente lato server, e solo al termine dell'elaborazione della pagina di destinazione la risposta viene inviata al client. Questo consente di mantenere un livello di accoppiamento maggiore tra le due pagine oggetto del trasferimento, perchè il server ha accesso ad entrambe nello stesso momento e poiché il tutto avviene in un unico ciclo di richiesta/risposta.

Prima di entrare nell'argomento, anche qui è necessario conoscere che anche il metodo Transfer presenta degli overload.

Nome Descrizione
HttpServerUtility.Transfer (String) Per la richiesta corrente, termina l'esecuzione della pagina ed inizia l'esecuzione della pagina specificata presente all'url specificato.
HttpServerUtility.Transfer (String, Boolean) Termina l'esecuzione della pagina corrente ed esegue la nuova pagina, consentendo di specificare se mantenere le informazioni presenti nelle collezioni QueryString e Form.
HttpServerUtility.Transfer (IHttpHandler, Boolean) Termina l'esecuzione della pagina corrente ed inizia una nuova richiesta utilizzando l'HttpHandler personalizzato specificato, consentendo di mantenere o meno le informazioni presenti nelle collezioni QueryString e Form.

Mentre non mi soffermerò sull'ultimo overload, che viene utilizzato in situazioni diverse da quelle oggetto di questo articolo, è interessante conoscere le prime due implementazioni.

Il parametro Boolean opzionale consente di specificare se mantenere o meno le informazioni presenti nelle Collections QueryString e Form della pagina nella quale è stato invocato il trasferimento. Se questo non viene specificato le informazioni vengono mantenute di default.

Questo comportamento consente di accedere alle informazioni presenti nella pagina precedente anche dalla pagina verso cui è effettuato il trasferimento, consentendo quindi alle due pagine di condividere informazioni.

Nella nuova pagina, quindi, sarà possibile utilizzare queste informazioni tramite le proprietà esposte dall'oggetto Page:

Request.QueryString["nomeParametroQueryString"];
Request.Form["nomeParametroForm"];

Se i vantaggi nel passaggio di dati si limitassero a questi, tuttavia, il metodo Transfer non sarebbe molto più interessante del precedente, da questo punto di vista.

Esiste infatti un altro modo per passare dati tra due pagine utilizzando questo metodo. Questa tecnica non è strettamente legata al metodo Transfer, ma è consentita dalle caratteristiche della classe HttpContext.

Questo oggetto, accessibile tramite la proprietà Context dell'oggetto Page, contiene tutte le informazioni correlate alla richiesta corrente, compresi gli oggetti Request, Response, Server e così via.

Oltre a tutte queste informazioni indispensabili, esso espone una proprietà, Items, che implementa l'interfaccia IDictionary ed è in grado di contenere un qualsiasi tipo di oggetto che vogliamo inserirvi.

La caratteristica importante di questo oggetto è che esso ha una durata di vita che va dall'inizio alla fine della richiesta corrente. Ciò consente di accedervi in qualsiasi punto della richiesta.

Mentre nella maggior parte dei casi la sua proprietà Items non risulta molto utile per memorizzare valori, poiché dato che la vita di una richiesta dura dalla richiesta della pagina al suo invio al client non è necessario conservare dati dal momento che dopo l'invio della pagina il suo contenuto sarebbe automaticamente eliminato, in questo caso, invece, in cui le pagine che vengono elaborate durante il ciclo di vita di un'unica richiesta sono invece due, la possibilità di memorizzare valori al suo interno durante l'esecuzione della prima pagina consente di averli ancora disponibili, e sempre nello stesso posto, anche durante l'esecuzione della seconda.

Per chiarire questo concetto, che a parole è molto più complicato che nella realtà, ecco un esempio.
La prima pagina inserisce valori all'interno dell'oggetto Context.Items e fa un transfer ad un'altra pagina:

Context.Items["nome"] = "valore";
Server.Transfer("nuovapagina.aspx");

La seconda, in un qualsiasi punto della sua elaborazione, può recuperarli allo stesso modo, poiché l'oggetto Context è lo stesso di prima, anche se acceduto da due istanze di pagina differenti:

Response.Write(Context.Items["nome"]; // stamperà la stringa "valore"

È importante tenere in considerazione che in questo modo è possibile condividere dati di qualsiasi tipo, poiché l'oggetto Items può contenere oggetti arbitrari. Naturalmente, per recuperarli, sarà poi necessario effettuare un cast opportuno per ottenere un oggetto del tipo desiderato. Però è evidente che dal punto di vista di condivisione di dati tra le due pagine in questione i vantaggi sono notevoli rispetto al metodo Redirect.

Osservazioni sulle prestazioni di rete

Questo aspetto è un altro vantaggio a favore del metodo transfer, poiché, come appare ormai evidente, esso non richiede un roundtrip aggiuntivo, ma tutta l'operazione è svolta sul server, e la risposta viene inviata un'unica volta per ogni invocazione del metodo.

Osservazioni sulla sicurezza

Poiché utilizzando questo metodo non viene effettuata una nuova richiesta al server ma si utilizzano i dati della richiesta originali, non è possibile verificare che il client sia autorizzato ad ottenere la risorsa che viene inviata a seguito di un Transfer. Nel caso in cui sia quindi necessario un controllo di questo tipo si dovranno prevedere delle tecniche di autenticazione ed autorizzazione personalizzate.

Osservazioni sull'usabilità

Come accennato in precedenza, utilizzando il metodo Transfer l'indirizzo visualizzato sulla barra degli indirizzi del browser non cambia ma resta quello della prima pagina. In alcuni casi questo può confondere l'utente.

Comportamenti insoliti

Per concludere l'argomento, vale la pena mettere in evidenza una situazione che può presentarsi le caso in cui la prima pagina scriva qualcosa sul buffer (ad esempio tramite Response.Write) dell'oggetto Response e questo non venga svuotato prima del trasferimento alla seconda pagina.

Distinguiamo tuttavia due casi:

  1. Il Buffer dell'oggetto Response è disattivato, ossia il contenuto viene inviato direttamente al client senza bufferizzazione;

  2. Il Buffer dell'oggetto Response è attivato, ossia il suo contenuto viene inviato tutto in una volta al termine dell'elaborazione della pagina.

I comportamenti che si ottengono sono i seguenti:

  1. In questo caso i dati della Response sono già stati inviati e non c'è modo di eliminarli. Di conseguenza nella pagina di destinazione l'output conterrà (in testa) anche quei dati che erano stati scritti dalla prima pagina;

  2. In questo caso, invece, con il metodo Response.Clear() è possibile (e consigliabile) eliminarli prima di eseguire il trasferimento.

Conclusioni

Come abbiamo visto anche due metodi comunemente utilizzati presentano delle caratteristiche interessanti che spesso non vengono tenute in considerazione durante la scelta di quale utilizzare. Essere al corrente delle peculiarità, dei vantaggi e degli svantaggi di ognuno consente di effettuare una scelta mirata.

In linea del tutto generale, i criteri guida per la scelta possono essere riassunti nei seguenti punti.

È necessario o preferibile utilizzare il metodo Redirect quando:

  • Si deve richiedere una risorsa non presente sullo stesso server della pagina iniziale;

  • Non è necessario un meccanismo avanzato di passaggio di parametri;

  • Non si hanno problemi di imbottigliamento della rete o di carico del server;

  • Non si necessita di un accoppiamento stretto tra le due pagine.

Viceversa, sarà necessario o preferibile utilizzare il metodo Transfer quando:

  • La risorsa è presente sullo stesso server della pagina iniziale ed è una web form (con estensione .aspx);

  • È necessario un meccanismo affidabile e più complesso di passaggio dei parametri;

  • Si vuole ridurre il carico della rete e del server;

  • Non ci sono requisiti di autenticazione ed autorizzazione relativi alla pagina di destinazione;

  • Si vuole mantenere un accoppiamento forte tra le due pagine, in modo che oltre ai valori passati esplicitamente condividano anche tutte le informazioni insite negli oggetti che hanno una durata di vita limitata alla durata della richiesta.