I siti web che dispongono di contenuti dinamici oltre ad avere numerosi vantaggi hanno il problema dell'indicizzazione delle pagine dinamiche nei motori di ricerca a causa della struttura complessa dell'URL.
Ciò che potrebbe confondere il motore di ricerca non è tanto la pagina dinamica in se, ma piuttosto i parametri passati in querystring.
La struttura complessa dell'URL comporta degli svantaggi sia per l'utente che avrà difficoltà a memorizzarli sia per l'indicizzazione sui motori di ricerca.
La soluzione al problema è l'utilizzo della tecnica URL Rewriting. È una tecnica ben nota agli utilizzatori del Web Server Apache, che consente al server di "riscrivere" gli indirizzi dinamici sostituendoli con indirizzi che sembrano riferirsi a pagine statiche.
In questo articolo vedremo come applicare questa tecnica con ASP.NET utilizzando la gli HttpModule e gli HttpHandler per intercettare le richieste fatte al server in modo da visualizzare URL più "amichevoli" sia per l'utente che per i motori di ricerca.
Gli indirizzi
Molti spider dei motori di ricerca incontrano difficoltà nell'indicizzare correttamente le pagine dinamiche, preferendo, invece, indirizzi statici che non fanno uso di caratteri speciali come ad esempio "?" o "&"
Ecco un esempio di indirizzo dinamico ostile agli spider dei motori di ricerca:
http://www.miosito.it/contenuti.asp?cat=2&cod=1
ed ecco invece un indirizzo "statico":
http://www.miosito.it/contenuti/viaggi/italia.aspx
Le pagine con indirizzo statico risultano indicizzate maggiormente rispetto a quelle dinamiche.
Con l'URL Rewriting riusciamo a risolvere questo inconveniente, anche se l'implementazione di questa funzionalità con il .Net Framework, e, in particolare con il web server IIS richiede un po' di esperienza e, una buona progettazione.
Un po' di teoria
Per implementare l'URL Rewriting il .NET Framework ci mette a disposizione i moduli HTTP (HttpModule) e gli Handler HTTP (HttpHandler).
L'HttpModule intercetta tutte le chiamate alle pagine di una applicazione, e l'HttpHandler gestisce particolari tipi di chiamate sostituendosi al gestore delle normali pagine.
Gli Handler HTTP permettono di intercettare ed elaborare le richieste http in ingresso in modo analogo al filtro ISAPI mentre i moduli HTTP permettono di modificare le richieste http in ingresso in funzione delle risorse dell'applicazione.
Prima di ASP.NET per ottenere questo livello di controllo utilizzando IIS eravamo costretti a creare estensioni o filtri ISAPI. Questo significava avere accesso alla configurazione del server, cosa che oggi non è più necessaria.
ASP.NET passa ciascuna richiesta in ingresso ai moduli http. Questa richiesta può passare attraverso diversi moduli http ma può essere elaborata da un solo handler che genera la risposta al'http in ingresso.
Una volta che l'handler ha concluso l'esecuzione e generato la risposta questa, viene passata a ciascun modulo prima di essere restituita al client.
La pratica
Passiamo alla pratica con due esempi utilizzando proprio HTTP Handler e HTTP Module per implementare l'URL Rewriting in ASP.NET.
URL Rewriting con gli HTTP Handler
Nel nostro esempio vogliamo gestire il sito di un'ipotetica agenzia di viaggi.
L'home page del sito presenterà una serie di link statici a pagine "inesistenti" come ad esempio "viaggi/roma.aspx" o "viaggi/madrid.aspx". Quando l'utente clicca su uno dei link vogliamo ridirezionare l'utente su una "vera" pagina fisica, ovvero "viaggi.aspx". Questa pagina raccoglierà la richiesta e ricercherà i dati all'interno di un database per restituire a video le informazioni relative al viaggio. L'aspetto interessante è che nella barra degli indirizzi l'URL sarà del tipo "viaggi/roma.aspx" anziché un URL complesso del tipo "viaggi/viaggi.aspx?cod1=xxx&cod2=yyy".
Passiamo al codice, i passaggi da seguire sono:
- Scrivere una classe per creare un'istanza di HttpHandler, attraverso l'implementazione dell'interfaccia
- IHttpHandlerFactory
- Implementare i metodi
GetHandler()
eReleaseHandler()
- Settare le proprietà dell' handler nel web.config
- Creare la pagina .aspx, che elabora la richiesta e visualizza le informazioni desiderate.
Creiamo un httpHandler implementando l'interfaccia IHttpHandlerFactory
. In base all'URL che si riceve si decide quale Handler istanziare e gli si passa la chiamata.
Questa interfaccia espone due metodi:
GetHandler()
restituisce un oggetto di tipo IHttpHandler che elabora la richiesta.ReleaseHandler()
utilizza un'istanza già esistente dell'HttpHandler.
Da visual Studio o VWD apriamo un nuovo sito web e aggiungiamo una classe .cs al progetto che nominiamo "mioRewrite.cs".
Listato 1. La classe mioRewrite
using System;
using System.Web;
using System.Web.UI;
using System.IO;
public class mioRewrite : IHttpHandlerFactory
{
public IHttpHandler GetHandler(HttpContext context, string requestType, string URL, string pathTranslated)
{
context.Items["fileName"] = Path.GetFileNameWithoutExtension(URL).ToLower();
return PageParser.GetCompiledPageInstance(URL, context.Server.MapPath("viaggi.aspx"), context);
}
public void ReleaseHandler(IHttpHandler handler){ }
}
Il metodo GetHandler()
riceve come parametri l'istanza della classe HttpContext
della richiesta corrente e l'URL della richiesta. Le operazioni che abbiamo eseguito finora nel metodo GetHandler()
sono semplicemente il salvataggio del nome della pagina richiesta all'interno dell'istanza della classe HttpContext
, e la chiamata al metodo GetCompiledPageInstance
, che ci permette di specificare la pagina .aspx che andrà ad elaborare la richiesta.
Compiliamo la classe con il Prompt dei comandi di Visual Studio 2005.
Ora bisogna configurare in modo opportuno il web.config per aggiungere il riferimento all'handler e al database che utilizzeremo.
Listato 2. Dichiarazioni nel web.config
<?xml version="1.0"?>
<configuration>
<system.web>
<httpHandlers>
<add verb="*" path="viaggi/*.aspx" type="mioRewrite, mioRewrite"/>
</httpHandlers>
<compilation debug="true"/>
</system.web>
<appSettings>
<add key="dbprovider" value="Provider=Microsoft.Jet.OLEDB.4.0;Data Source="/>
<add key="dbpath" value="App_Data/db_viaggi.mdb"/>
</appSettings>
</configuration>
Ogni pagina .aspx richiesta, contenuta nella cartella viaggi, verrà gestita dal nostro Handler.
Aggiungiamo al sito la pagina Default.aspx che conterrà dei link a pagine "inesistenti"
Listato 3. La pagina Default.aspx
<form id="form1" runat="server">
<div id="partenze">
Partenze da Milano<br />
<br />
Italia<br />
<br />
Roma - <a href="viaggi/roma.aspx">Voli per Roma</a><br />
Rimini - <a href="viaggi/rimini.aspx">Voli per Rimini</a><br />
<br />
Estero<br /><br />
Madrid - <a href="viaggi/madrid.aspx">Voli per Madrid</a><br />
Amsterdam - <a href="viaggi/amsterdam.aspx">Voli per Amsterdam</a><br />
</div>
</form>
Per questo esempio utilizziamo un database molto semplice con un'unica tabella che contiene le informazioni relative alle singole pagine richieste.
A questo punto aggiungiamo la pagina "viaggi.aspx" nella cartella "viaggi". Questa sarà la pagina che mostrerà le informazioni richieste dall'utente. Apriamo la pagina appena creata e scriviamo il codice per la lettura dei dati dal DB.
Listato 4. La pagina viaggi.aspx
public partial class viaggi : System.Web.UI.Page
{
private OleDbConnection conn = null;
protected void Page_Load(object sender, System.EventArgs e){
string qualePagina =this.Context.Items["fileName"].ToString();
string strConn = System.Configuration.ConfigurationManager.AppSettings["dbprovider"] + "" + this.Page.Server.MapPath("../" + System.Configuration.ConfigurationManager.AppSettings["dbpath"]);
string sql = "SELECT * FROM Viaggi WHERE pagina='"+qualePagina+"'";
try
{
using(conn = new OleDbConnection(strConn))
{
// Riempiamo un dataset con i dati
conn.Open();
OleDbCommand cmd = new OleDbCommand(sql, conn);
DataSet ds = new DataSet();
new OleDbDataAdapter(cmd).Fill(ds, "viaggi");
// Scriviamo i dati a video
foreach(DataRow miorec in ds.Tables["viaggi"].Rows) {
Response.Write("<strong>"+miorec["Titolo"].ToString()+"</strong><br/>");
Response.Write(miorec["descrizione"].ToString()+"<br/>");
Response.Write(miorec["prezzo"].ToString() + " EURO <br/>");
Response.Write("<img src='../images/" + miorec["img"].ToString() + "'/><br/>");
}
}
}
catch(Exception errore){
Response.Write (errore.Message.ToString());
}
}
}
Nel Page_Load di recuperiamo il nome della pagina richiesta dall'utente con la seguente istruzione
string qualePagina =this.Context.Items["fileName"].ToString();
Quindi ricerchiamo la pagina all'interno del nostro database access e mostriamo a video il contenuto dei vari campi della tabella.
Ora eseguiamo il progetto, clicchiamo su voli per Roma, a video apparirà la seguente schermata con il risultato della nostra ricerca.
Se guardiamo l'URL nella barra degli indirizzi vediamo che la pagina visualizzata è "viaggi/roma.aspx" ma in realtà questa pagina non esiste.
URL Rewriting con HTTPModules
In questo esempio eseguiamo un semplice "rewrite dell'URL" con l'utilizzo di un Modulo Http personalizzato.
Analogamente all'esempio precedente abbiamo una pagina aspx che, a seconda del parametro che gli viene passato per mezzo della querystring, visualizza di volta in volta le informazioni desiderate. Nel nostro caso l'utente dovrà scegliere un prodotto da una lista, per visualizzare il catalogo relativo al prodotto selezionato.
Sulla barra degli indirizzi non vogliamo mostrare l'URL contenente la Querystring, ma vogliamo riscrivere l'URL in modo tale da renderlo più comprensibile sia all'utente, sia al motore di ricerca.
I passaggi da seguire sono:
- Scrittura di un modulo HTTP personalizzato
- Implementazione dei metodi Init () e Dispose() dell'interfaccia IHttpModule
- Registrazione del modulo all'interno del web.config
- Creazione della pagina .aspx, che elabora la richiesta e visualizza le informazioni desiderate.
Per creare un modulo HTTP creiamo una classe che implementa IHttpModule. Da visual Studio 2005 o VWD apriamo un nuovo sito web e aggiungiamo una classe .cs al progetto che nominiamo "mioRewrite.cs".
Listato 5. La classe mioRewrite.cs
public class mioRewrite : IHttpModule {
public void Init(HttpApplication App){
App.BeginRequest += new System.EventHandler(Rewrite_URL);
}
ASP.NET gestisce un pool di oggetti HttpApplication
per ogni applicazione in esecuzione e ogni istanza di questo pool, è responsabile di una richiesta. Questa richiesta, dovrà attraversare diversi moduli prima di raggiungere l'handler (come mostrato all'inizio dell'articolo).
Il metodo Init
dell'interfaccia IHttpModule
consente di inizializzare un modulo e di prepararlo alla gestione delle richieste. Nel nostro esempio, nel metodo Init
registriamo l'handler di evento BeginRequest
e gli associamo il metodo Rewrite_URL
.
L'evento BeginRequest
permette l'attivazione del metodo ad ogni richiesta di pagina, in modo tale da intercettare la chiamata, e permettere la sostituzione dell'URL. Scriviamo, ora, il codice del metodo Rewrite_URL()
Listato 6. Il metodo Rewrite_URL()
public void Rewrite_URL(object sender, System.EventArgs args)
{
HttpApplication App = (HttpApplication) sender;
string PagRichiesta = App.Request.Path.ToLower();
if (PagRichiesta.ToLower() == "/URLrewriting_module/libri.aspx")
{
App.Context.RewritePath("default.aspx?tab=1");
}
else if (PagRichiesta.ToLower() == "/URLrewriting_module/cd.aspx")
{
App.Context.RewritePath("default.aspx?tab=2");
}
}
Il metodo Rewrite_URL
intercetta ed elabora le richieste. In caso di corrispondenza tra gli URL riscrive i percorsi utilizzando il metodo RewritePath()
.
Il metodo Dispose()
è quasi sempre vuoto e serve per fare pulizia e rilasciare le risorse utilizzate:
Listato 7. Il metodo Dispose()
public void Dispose() { }
Passiamo alla scrittura della pagina "default.aspx". La pagina è molto semplice, la cosa importante è che contiene i link agli indirizzi fittizi che saranno filtrati dal nostro modulo.
Listato 8. La Web Form
<form id="form1" runat="server">
<div>
<asp:Label ID="lbltext" runat="server" Text="Seleziona un prodotto:" /><br/>
<br/>
<asp:HyperLink ID="HyperLink1" runat="server"
NavigateURL="libri.aspx">Libri</asp:HyperLink>
</div>
<asp:HyperLink ID="HyperLink2" runat="server"
NavigateURL="cd.aspx">CD musica</asp:HyperLink><br/>
<br/>
<br/>
</form>
All'interno del Page_Load()
verifichiamo quale link è stato selezionato.
Listato 9. Page_Load della pagina "default.aspx"
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (Request.QueryString["tab"]==null)
lbltext.Text = "Seleziona un prodotto:";
else
lbltext.Text = "Hai selezionato il prodotto con codice " + Request.QueryString["tab"];
}
...
Qui controlliamo se è valorizzato il parametro "tab" contenuto nella Querystring e mostriamo il suo valore a video.
Infine, aggiungiamo il riferimento al modulo all'interno del file di configurazione web.config.
Listato 10. Modifiche al file di configurazione
<httpModules>
<add type="mioRewrite" name="mioRewrite"/>
</httpModules>
Ora possiamo eseguire l'applicazione. Selezionando uno dei prodotti dalla lista possiamo notare che siamo in grado di leggere dalla QueryString
il parametro relativo al prodotto selezionato (tab), senza mostrarlo nella barra degli indirizzi.
Gestire i postback
Se si fa utilizzo del meccanismo di postback nell' applicazione allora bisognerà tener presente che con il postback si verifica un problema, l'URL sottostante verrà visualizzato vanificando il lavoro dell'URL rewriting.
Il problema è che il rendering del tag <form>
imposta l'attributo action
con l'URL memorizzato nell'oggetto Request
, l'utente dopo il postback vedrà, quindi un URL di questo tipo:
URLRewriting_Module/default.aspx?tab=1
Per risolvere il problema possiamo fare in modo che non venga eseguito il rendering dell'attributo action. Una strada è quella di estendere la classe System.Web.HtmlControls.HtmlForm
, eseguendo l'override del metodo RenderAttribute()
e modificare la linea di codice relativa al rendering dell'attributo azione.
Listato 11. Estendere HtmlForm e rimuovere l'attributo action
public class Form : System.Web.UI.HtmlControls.HtmlForm
{
protected override void RenderAttributes(HtmlTextWriter writer)
{
writer.WriteAttribute("name", this.Name);
base.Attributes.Remove("name");
writer.WriteAttribute("method", this.Method);
base.Attributes.Remove("method");
this.Attributes.Render(writer);
base.Attributes.Remove("action");
if (base.ID != null)
writer.WriteAttribute("id", base.ClientID);
}
}
In questo modo riusciamo ad evitare spiacevoli sorprese all'utente che potrebbe vedere cambiare l'URL improvvisamente.
Conclusioni
In questo articolo abbiamo introdotto la tecnica dell' URL Rewriting presentando due esempi pratici che mostrano l'utilizzo dei moduli http e degli Handler http con ASP.NET.
La tecnica dell'URL Rewriting è molto utile, perchè rende la vita più facile sia all'utente che non deve ricordarsi URL complessi sia ai motori di ricerca che riescono ad indicizzare al meglio le pagine dinamiche che presentano parametri nella querystring.
Fino a poco tempo fa Google stesso invitava i webmaster a non utilizzare i parametri in querystring poiché non sarebbero state indicizzate correttamente le pagine. Ultimamente l'algoritmo Googlebot è stato modificato in modo tale da riuscire ad indicizzare anche questo tipo di pagine.
Però, a parte questo, mi sento di consigliare ancora la tecnica dell'URL rewriting, perché la maggior parte degli spider ancora soffre di questo problema e, poi, perché la presenza di keyword rilevanti all'interno dell'URL porta sempre vantaggi per il posizionamento.