di .NET e di altre amenità

ASP.NET 2.0: Url Rewriting

Curiosamente stamane Simone mi ha preceduto di un soffio nel parlarvi di Url Rewriting. In questi giorni infatti mi sono trovato nella necessità di soddisfare una particolare esigenza di rewriting che mi ha fatto approfondire l'argomento e avevo in mente di scrivere questo post da un po'. Preso atto che la scelta di Simone è ricaduta sull'implementazine di un IHttpModule, è evidente che il mio post non è una ripetizione perchè in realtà il metodo che ho usato è basato su una tecnica che nell'articolo di MSDN che Simone ha citato viene appena sfiorata. Eccomi quindi a proporvi il metodo che ho recentemente utilizzato per il rewriting dell'url in una applicazione che vedeva le proprie pagine riportate parzialmente nel database e parzialmente sul filesystem.

La mia implementazione di rewrite è basata sulla creazione di un HttpHandlerFactory. Se consideriamo un HttpModule come un filtro che intercetta tutte le chiamate alle pagine di una applicazione, e un HttpHandler un gestore per particolari tipi di chiamate che si sostituisce a quello delle normali pagine, la HttpHandlerFactory invece è una classe che viene usata dal runtime per istanziare un HttpHandler specifico in base alla chiamata che esso riceve. Implementando l'interfaccia IHttpHandlerFactory, in sostanza ci si pone nella HttpPipeline al livello più alto possibile e si è in grado di decidere quale handler istanziare in base all'url che si riceve ed in seguito passare ad esso la chiamata.

Questa condizione nel mio caso era davvero esemplificativa. Si trattava infatti di effettuare una semplice query nel database - query che con l'implementazione di un meccanismo di caching è diventata unica in tutta la vita dell'applicazione - e in base al risultato decidere se istanziare l'handler delle pagine aspx normalmente oppure variando leggermente l'input per ottenere oltre al rewrite, il caricamento di una specifica pagina in base a quanto specificato nel database. Vedo di spiegarmi meglio; tanto per cominciare vediamo come fa il runtime ad istanziare l'handler per le pagine aspx.

IhttpHandler page = PageParser.GetCompiledPageInstance(url, pathTranslated, context);

Per mezzo di questa semplice riga, è possibile indicare al runtime di produrre una istanza di IHttpHandler del tutto uguale a quella che produce esso stesso in condizioni normali. I parametri indicano il path virtuale, il path fisico della pagina e l'HttpContext della chiamata. Ecco quindi che in realtà utilizzando tale metodo si può "imbrogliare" il runtime facendo caricare ad esso un file che si trovi in un percorso diverso da quello reale che esso ha calcolato sulla base della chiamata. Il trucco per fare il rewriting in questo caso è di avere una singola pagina "dbpage.aspx", nella root dell'applicazione cui redirigere tutte le chiamate che fanno riferimento al database e - nel mio caso - rifilare al runtime una pagina "fisica" scelta fra una rosa di possibili layout per mezzo di un campo nel database stesso. Ecco il codice:

public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
{
    PortalPage page = 
this.Indexer.FindPage(url);

    
if (page != null)
    {
        context.Items["CurrentPage"] = page;
        
return PageParser.GetCompiledPageInstance("~/dbpage.aspx", this.MapLayout(page.Layout), context);
    }

    
return PageParser.GetCompiledPageInstance(url, pathTranslated, context);
}

public string MapLayout(string layoutName)
{
    
return string.Format("~/layouts/{0}.aspx", layoutName);
}

Il codice parla da solo, ma vediamo lo stesso di spendere due parole. La prima riga reperisce l'istanza della PortalPage da un oggetto tenuto in cache, che rappresenta la struttura caricata dal database una volta per tutte. A questo punto, se la pagina viene trovata essa viene inserita nel contesto della chiamata e poi viene effettuato il rewrite modificando l'url in ~/dbpage.aspx e passando come path fisico una diversa pagina aspx - che per inciso contiene una serie di WebPartZones in un ContentPlaceHolder. Nel caso in cui la pagina non esista nel database, i parametri vengono passati così come sono ricevuti al PageParser che quindi costruirà l'IHttpHandler consueto.

Risultato di questo giochetto è che parte delle pagine dell'applicazione sono definite in una tabella del database e l'utente potrà navigare tale struttura come se fosse fusa con quella sul filesystem stesso.

Non mi rimane di segnalarvi che questa tecnica è utilizzabile anche con il framework 1.1, praticamente senza alcuna modifica.

powered by IMHO 1.3

Commenti

2006-01-11T01.23.00+01.00 #

Di .NET e di altre amenita'

2006-02-14T19.38.00+01.00 #

Ciao,
e` la prima volta che leggo questo blog, devo ammettere che lo ho trovato su google in quanto sto affrontando un problema simile (framework 1.1 pero`).
Premetto anche che lavoro da poco come programmatore ed ho molto da imparare...

Io ho utilizzato un metodo simile ma non in tutto...
Ho sostituito letteralmente l'handler di default nel web.config con uno mio per tutto il sito tranne che in una directory:
<httpHandlers>
    <add verb="*" path="pages/*.aspx" type="System.Web.UI.PageHandlerFactory" />
      <add verb="*" path="*" type="HttpHandlers.Factories.DefaultHandlerFactory, mindlog" />
    </httpHandlers>

Inoltre il DefaultHandlerFactory va alla ricerca di un altro handlerFactory in base all'url.
Esempio:
www.dominio.it/articles/123/
DefaultHandlerFactory va a cercare un Handler per /articles/ e passa la gestione a quello corretto. Come?
Ogni classe handler ha un attributo in testa che determina, tramite regular expressions, gli url che puo` gestire:
[HttpHandlers.Attributes.Default(@"^/articles")]

A questo punto l'handlerFactory decide se gestire direttamente la cosa (passandola ad un altro handler) oppure passarla ad un altro handlerFactory.

L'Handler "finale" generalmente fa un Server.Transfer verso una pagina aspx sotto /pages/....


So di non essere stato molto chiaro, cmq ora provero` anche il tuo metodo, che sinceramente mi sembra migliore... ^^;


Saluti

Michele Gee

2006-09-11T23.25.00+01.00 #

ciao ho trovato il tuo articolo su ugidotnet molto interessante e utile.

Da qualche giorno sto cercando di sviluppare un urlrewriting simile, con la sola differenza di utilizzare un SQLSiteMapProvider invece che utilizzare quello XML.

Il problema maggiore che ho riscontrrato, è quello di aggiungere al nodo del SiteMap gli attributi regexUrl, e phisicalFileUrl.

Naturalmente utilizzando la classe SiteMapNode, posso inserire solo gli attributi di default quali id, title, description e url.
hai qualche suggerimento in merito??

ciao e grazie mille

cristian

2006-09-13T22.07.00+01.00 #

prova a vedere questo articolo. credo faccia al caso tuo.
http://www.xedotnet.org/40/section.aspx/181

Andrea Boschin

2006-09-14T02.32.00+01.00 #

Ti ringrazio per il link, gli sto dando un occhio anche se devo dire che comunque la faccenda è abbastanza spinosa e impegnativa...

Volevo poi segnalarti un problemino sul tuo codice di esempio dell'url rewriter.

Nel momento in cui nel nodo regexUrl inserisco dei valori di tipo stringa, al momento di recuperarli passando il tipo string all'httpHandler, la classe httpUtils mi restituisce l'eccezione per il cast.

Come mai?
grazie

cristian

2006-09-22T18.08.00+01.00 #

ciao
Il mio progetto di url rewriting è andato avanti, specialmente grazie al tuo articolo, ora però sto cercando di implementare alcune funzionalità, tra cui impostare un diverso SiteMap, questo specialmente se si gestiscono più sitemap, ad esempio uno per ogni lingua del sito.

Il problema è che non riesco a capire dove agire, sempre che si possa fare.

Ho aggiunto diversi SiteMap nel webConfig, e come unica soluzione ho pensato di modificare il Default SiteMap, ma onestamente ho diversi dubbi ed errori...

Hai qualche idea da consigliarmi?
ciao e grazie mille

cristian

2006-09-23T08.49.00+01.00 #

se stai usando la mia sitemap così come l'ho postata, dovresti essere in grado di creare diverse sitemap utilizzando per ognuna di esse una diversa applicationkey. in realtà il runtime gestisce una sola sitemap ma è abbastanza intelligente da sfruttare esclusivamente il ramo relativo l'applicationkey, derivato dall'url corrente dell'applicazione.

Andrea Boschin

2009-07-30T22.11.48+01.00 #

Hi Andrea

Mi piacerrebbe fare una chiaccherata con te perche' hai affrontato molti dei problemi che mi hanno fatto dannare. In pratica ho la necessita' (sorvoliamo il perche') di salvare l'intero sito in un database comprese le pagine aspx. Per risolvere in modo ottimale questa situazione dovrei fare appello alla tecnologia VirtualPathProvider di cui sei a conoscenza ma che, per mia sfortuna, non mi funziona perche' il mio sito risulta precompilato....
Tuttavia anche un IHttpHandler risolverebbe il mio caso se non quando ho a che fare con le pagine .aspx. Tu sopra mostri un codice che ho capito all' 80% ma mi sfugge qualcosa.
Se fosse esistita una funzione del tipo:
IhttpHandler page = PageParser.GetCompiledPageInstance(url, stream, context);
avrei potuto prelevare il contenuto della pagina aspx dal database e "darlo in pasto" attraverso uno stream alla funzione soprastante senza beneficio di inventario. Come potrei ovviare a questa ideale soluzione ?
Dove risiede il contenuto della pagina aspx nella tua soluzione ? Devo forse ogni volta riversare il suo contenuto in un file da te definito in MapLayout ?
Grazie anticipatamente per la tua attenzione.
Ciao Maurizio P.

Maurizio Poletto

2009-09-03T20.27.27+01.00 #

Ciao Andrea
Tornato dalle vacanze?
Come puoi vedere tra l'email inviate, tempo fa ti avevo inviato un'email con un problema non proprio semplice. Durante le vacanze (si fa per dire) ho provato a utilizzare la soluzione dell' IHttphandler ma ho dovuto arrendermi poiche' non e' facile utilizzare la soluzione che tu proponi in quanto la pagina aspx ha il file codebehind separato. Esisterebbe una soluzione ma la trovo "error prone" ovvero quella di "salvare" il codice c# nella pagina aspx stessa.
A questo punto vorrei ritornare alla vecchia proposta ovvero quella di utilizzare VirtualPathProvider. Ho gia' implementato il codice ma non mi funziona ovvero le sole pagine che vengono 'intercettate' sono quelle con l'estensione .aspx ma non .html. Non capisco dove sto sbagliando visto che ho seguito alla lettere tutti gli esempi proposti. Mi puoi dare una dritta ?
Ciao
Maurizio

Maurizio Poletto

2009-09-03T20.45.30+01.00 #

Bisogna che ci sentiamo via mail. Senza codice alla mano non sono in grado di aiutarti. Mandami una mail tramite la contact form oppure chiamami al messenger con la web presence.
Ciao

Andrea Boschin

Aggiungi commento




  Country flag

biuquote
  • Commento
  • Anteprima
Loading