URL rewriting v IIS a ASP.NET

URL rewriting, neboli přepisování URL, je jednou z technik, jak udělat adresy dynamicky generovaných stránek "uživatelsky přívětivější". Nebo spíš "SEO-přívětivější", v mnohých případech. Způsobů, jakými lze v ASP.NET a IIS rewriting realizovat, je celá řada. A stejně tak je celá řada problémů, s nimiž se budeme muset při nasazení rewritingu potýkat. Tento článek shrnuje dostupné možnosti a nabízí řešení obvyklých problémů.

Co je URL rewriting

Adresa stránky, URL, představuje ve svém prapůvodním určení název serveru a fyzickou cestu k souboru na jeho disku. Nicméně většina webů dneška je dynamicky generovaných, takže typicky oněch "názvů souborů" zase až tolik není. Aplikace typicky sestává z několika málo stránek, data se načítají z databáze a důležitý je parametr (typicky nějaké numerické ID), který se předává v query stringu.

Adresa pak vypadá v lepším případě nějak jako http://www.vlada.cz/scripts/detail.php?id=47089, v horším případě pak takto. Někteří experti jsou schopni udělat celý web tak, že se schovává za jedinou URL s nicneříkajícím parametrem. Zářným příkladem budiž třeba Student Agency s URL v duchu http://www.studentagency.cz/mainpage.php?switch=1460, takže podle adresy nepoznáte nic a musíte se proklikávat zmatenou navigací webu, jehož autor by zasloužil veřejně zmrskat.

Obecně je tedy žádoucí, aby URL byla tak nějak přiměřeně "hezká". Adresa by měla alespoň přibližně napovídat, co se za ní skrývá. Ne snad proto, že by se předpokládalo, že ji uživatel bude do adresního řádku kompletně psát celou (i když v některých případech se i to může stát), ale spíše začne psát adresu kde už byl a prohlížeč mu ji nabídne pomocí autocomplete. Z hlediska optimalizace pro vyhledávače může být užitečné do adresy stránky zadat klíčová slova. I když ani to by se nemělo přehánět.

Samozřejmě, že kvůli "hezkým" adresám nebudeme na straně serveru generovat stovky souborů a vytvářet složitou databázovou strukturu. Právě proto je zde URL rewriting: analyzuje zadanou adresu, vytáhne z ní potřebné identifikátory a interně ji změní – přepíše – na tu jednoduchou, systémovou, kterou uživateli raději neukazujeme.

Základní možnosti rewritingu

Způsobů, jak rewriting realizovat a dostupných řešení je spousta. Počínaje URL rewriting modulem pro web server, přes rozličná hotová řešení až po vlastní HTTP modul, který bude aplikovat pravidla dle vašich potřeb. V následujícím textu se zaměřím na použití URL Rewrite modulu pro IIS 7.0 a na napsání vlastního modulu.

URL Rewrite Modul pro IIS 7.0

Po uvedení IIS 7.0 se na microsoftím webu IIS.net objevila celá řada zajímavých rozšiřujících modulů. Jedním z nich je i modul pro URL rewriting:

Součástí instalace je kromě vlastního modulu též addin to IIS managera pro jeho vizuální správu, kde můžete nastavovat jednotlivá pravidla. Ta jsou v zásadě založena na regulárních výrazech, přičemž jednotlivé captures (části URL vyhovující zadaným podmínkám) lze předávat do parametrů vnitřního URL jako query string proměnné.

Hlavní výhodou a zároveň též nevýhodou tohoto modulu je, že v zásadě nemá nic společného s ASP.NET ani jinou serverovou technologií pro vývoj stránek. Tyto technologie o jeho existenci v zásadě nevědí a nijak s ním neinteragují, přijdou už k "hotové" vnitřní adrese. Jediný způsob, jak se mohou dozvědět o tom, že uživatel zadal jinou adresu, je podívat se na speciální HTTP hlavičku HTTP_X_ORIGINAL_URL. To může znamenat problémy při vytváření relativních odkazů v aplikaci, odesílání formulářů a podobně. Finální (RTM) verze URL Rewrite Modulu tento problém částečně řeší (narozdíl od všech předchozích verzí, včetně verze GoLive). Želbohu ale vskutku pouze částečně, takže některé věci fungují, některé ne.

Tento rewritingový modul je velice mocný, umožňuje psaní pokročilých struktur pomocí regulárních výrazů, rewrite maps (rozsáhlé a zvlášť udržované kolekce název:hodnota) a dokonce import pravidel z mod_rewrite pro Apache, což může usnadnit portaci aplikací. Kromě rewritingu umí požadavky též přesměrovávat nebo blokovat.

Další výhodou je přítomnost grafického rozhraní pro správu, které vám pomůže vytvořit sadu pravidel, aniž byste museli složitě řešit syntaxi.

Vlastní rewritingový modul

Pokud vám vestavěný rewritingový modul nevyhovuje, můžete si napsat vlastní. Stačí napsat běžný HTTP modul, a odchytit si některou z počátečních událostí – typicky BeginRequest. A v ní pak zavolat metodu RewritePath aktuálního HttpContextu.

Pokud to uděláte takhle, pak umíte totéž, co URL rewrite modul. A s týmiž chybami.

Pokud chcete dosáhnout toho, aby se vám veškeré odkazy atd. generovaly správně, musíte rewriting provést dvakrát. Jednou na začátku požadavku a podruhé před zavoláním handleru stránky – tj. typicky v PreRequestHandlerExecute. Tím dotyčný handler zmatete natolik, že si bude myslet, že jeho adresa je ta, kterou zadal uživatel, což elegantně vyřeší veškeré problémy. Až na jeden.

Problém s query stringem

Typicky se k předávání parametrů používá query string, tedy parametry v URL. Mnohdy se ale právě jim chceme rewritingem vyhnout. Na druhou stranu, jejich zpracování ve stránce je snadné programově (pomocí Request.QueryString["…"]) i deklarativně (pomocí <asp:QueryStringParameter … />). Nabízí se tedy snadné řešení: navenek vystavit na odiv hezkou URL (např. /Articles/123-titulek.aspx) a interně to přepsat na staré známé /Article.aspx?id=123.

Funguje to celkem dobře, ale jenom do chvíle, kdy je žádoucí kombinovat tento přístup s opravdovými query string parametry. To je účelné například při stránkování (a standardní ASP.NET prvek DataPager to ani jinak bez JavaScriptu neumí) nebo v některých dalších případech. Příkladem z praxe budiž třeba náš video archiv, s adresami typu http://videoarchiv.altairis.cz/Categories/7-asp-net.aspx?Page=2 – chceme zobrazit druhou stránku položek z kategorie číslo 7 (ASP.NET). Pokud použijeme pro předávání "interních" parametrů také query string, nebude stránka vědět, které parametry jsou které.

Řešením je pro předávání "interních" parametrů nepoužívat query string, ale něco jiného. Tím "něčím jiným" může být například kolekce Items aktuálního HttpContextu. Tato kolekce slouží jako univerzální úložiště sdílené všemi, kdo se na zpracování požadavku podílejí, všemi HTTP moduly a handlery. Koncepčně je podobná třeba Session nebo Cache – stringový klíč, objektová hodnota. Narozdíl od nich ale přetrvává pouze po dobu zpracování tohoto jednoho požadavku, ne déle.

Při rewritingu tedy můžeme uložit požadované parametry sem a ve stránce je zase načíst. Procedurální načítání je triviální, ale neexistuje žádný hotový control pro deklarativní přístup, např. při deklarativním data bindingu. Můžeme ale použít přístup diskutovaný v jiném článku na tomto webu a napsat si vlastní control, který to zařídí:

/// <summary>

/// Represents an <see cref="System.Web.HttpContext.Items" /> parameter.

/// </summary>

public class ContextItemParameter : Parameter {

 

    /// <summary>

    /// Key of item in <see cref="System.Web.HttpContext.Items" /> collection.

    /// </summary>

    public string ContextItemField {

        get { return (string)this.ViewState["ContextItemField"]; }

        set { this.ViewState["ContextItemField"] = value; }

    }

 

    protected override object Evaluate(HttpContext context, Control control) {

        object itemValue = context.Items[this.ContextItemField];

        if (itemValue == null) return this.DefaultValue;

 

        try {

            return System.Convert.ChangeType(itemValue, this.Type);

        }

        catch (Exception) {

            return this.DefaultValue;

        }

    }

 

}

Samozřejmě je možné oba dva přístupy kombinovat, tj. prvotní rewriting provést pomocí URL Rewrite Modulu a druhý rewriting (a načtení parametrů) pak pomocí vlastního modulu. Podle mého názoru to ale poněkud postrádá smysl, ježto apsat i tu první část je celkem jednoduché.

Závěrem

URL rewriting je užitečná technika a pokud se používá s rozumem, může být všestranně užitečný. Existuje velmi schopný modul pro IIS, který ale za univerzalitu platí nízkou integrací s ASP.NET a jeho použitelnost je tedy omezená. Většinu problémů lze řešit dvojitým rewritingem a předáváním interních parametrů přes kolekci HttpContext.Current.Items.

Zde popisované řešení není ani zdaleka jediné. Podobného efektu lze s různou mírou pracnosti dosáhnout i jinými způsoby, z nichž některé jsou popsány například v dokumentaci ke zmíněnému modulu.

Mnou nabízené řešení je ale elegantní a v praxi se dlouhodobě osvědčuje – například i na webu, který právě čtete.

Titulek:
Text komentáře:
Vaše jméno:
Váš e-mail: (nebude zveřejněn)

WWW stránka:
Opište text z obrázku:
odpovědětodpovědět Gravatar

System.Web.Routing

Hezký článek, dobře použitý URL rewriting je určitě příjemným zpřehledněním webu.

Co si myslíš o rewritingu pomocí tříd z <a href="http://msdn.microsoft.com/en-us/library/…>System.Web.Routing Namespace</a>?

Zatím si o něm nemyslím nic. MVC Framework mne ničím nezaujal a použití routingu mimo něj jsem zatím nikde nezaznamenal.

Kromě toho, mám rád deklarativní postupy a routing je, pokud jsem ho správně pochopil, spíš procedurální záležitost, což mne přiměřeně odpuzuje.

odpovědětodpovědět Gravatar

MVC URL routing

18.12.2008 10:23:0818.12.2008 10:23:08 HonzaHonza ---.znet.vse.cz

Na tohle téma mi přijde pěkné řešení, které nabízí ASP.NET MVC URL Routing (nyní už oficiální beta...). Hodně detailní článek od Scotta Guthrie:

http://weblogs.asp.net/scottgu/archive/2…

Jak jsem psal v odpovědi na předchozí komentář: použití MVC mi přijde jako příliš velká daň za hezká URL a mimo MVC jsem ještě routing nikde neviděl.

odpovědětodpovědět Gravatar

RE: MVC URL routing

20.12.2008 12:19:0220.12.2008 12:19:02 PetrPetr ---.67.broadband.iol.cz

A co se ti nelíbí na MVC Framework? Me přijde výborná ta kontrola nad tím co leze za html. /nejen to/

Výsledné HTML je mně dost ukradené, důležité je, že to funguje. Mně se líbí deklarativní programování, tj. všechno dělají "chytré" server controls a stránka sama je "hloupá", nic moc v ní není. MVC jde opačným směrem a s tím já nejsem mentálně kompatibilní. Nicméně to je můj osobní přístup, který nevypovídá nic o objektivních vlastnostech té technologie.

odpovědětodpovědět Gravatar

ASP.NET Routing a WebForms

Rád bych se trochu zastal ASP.NET Routingu a vysvětlil, že pro WebForms je to také velice dobrá technologie..:-)

ASP.NET Routing je samostatná komponenta nezávislá na ASP.NET MVC, která je velmi dobře použitelná i pro ASP.NET WebForms. Stačí si k ní připsat odpovídající Handler.

Není dále pravda, že by to nešlo nakonfigurovat deklarativně. V základu sice nic takového není, a ve všech demech se zatím používá přímé zadání přes metody příslušné třídy v souboru Global.asax, ale takováto konfigurace např. z XML souboru jde rovněž velmi snadno dodělat.

(Důvod, proč to nejde již v základu je podle mne ten, že to autoři jednoduše "ještě" neudělali, aby to mohli dřív vydat..)

Možná se ptáte, v čem je ASP.NET Routing lepší oproti IIS 7 URL Rewritingu. Důvod je takový - ASP.NET Routing vám umožňuje všechny vaše odkazy generovat automaticky, protože zná místo, odkud může "spočítat" výslednou adresu.

Příklad:

Mám předpis cesty ("Route") URL v podobě: "clanky/{webIdClanku}"

... který mi zajišťuje, že když návštěvník navštíví stránku "http://www.mojedomena.cz/clanky/proc-je-…,

tak ten požadavek ve skutečnosti vyřídí formulář (!) "~/Pages/Articles.aspx" s parametrem "webIdClanku" v GET nastavenem na "proc-je-dneska-tak-hnusne"

... no, to zatím není nic neobvyklého. Ale teď to zajímavé - když chcete z nějaké jiné stránky na tuto stránku odkazovat, nenapíšete ten odkaz "natvrdo", ale použijete např. ovládací prvek DynamicUrl (imaginární), kterému zadáte v parametru, ze chcete najít URL, která vás dovede k článku "proc-je-dneska-tak-hnusne" podle parametru "webIdClanku".

Ten ovládací prvek se podívá do tabulky Routes a najde tam Route, která používá jako parametr "webIdClanku". Pak uz jen dosadi a vrati vyslednou URL...:-)

Řekl jsem to velmi zjednodušeně, ale princip systému to vystihuje.

Ve výsledku vám to přinese např. toto významné zlepšení - Představte si, že chcete vaše URL na webu nějak změnit. Normálně by to byl obrovský problém, protože existují až stovky vnitřních odkazů, které byste tím porušili. S ASP.NET Routing vám ale stačí změnit jedinou věc - "Routes" (cesty), které vaše URL definují.

Možností využití je ale opravdu spoustu. Otevírá se třeba i možnost vytvářet opravdové nice-url-enabled univerzální (!) server controlly právě s pomocí ASP.NET routingu (IIS URL Rewrite vám např. neumožní napsat opravdu univerzální Paginator, který by generoval "hezké odkazy").

odpovědětodpovědět Gravatar

RE: ASP.NET Routing a WebForms

5.1.2009 10:06:285.1.2009 10:06:28 PeterPeter 85.248.66.---

Hmm rozpisal si sa pekne, dolozis niekde ukazku? Pripadne poslat emailom.

Vdaka

odpovědětodpovědět Gravatar

řešení VirtualPathProvideru + SiteMapProvideru

Zdravím, cca před rokem jsem intenzivně řešil možnosti rewritingu pod ASP.NET 2.0 / IIS6 ... Napsal jsem si nejdříve vlastní modul a spousta věcí nefungovalo: někdy postbacky, jindy prvky ASP.NET jako asp:menu, asp:treeview pro zobrazení navigace na stránkách. (na webu jsem totiž používal web.sitemap) Bylo to dost nepoužitelné. Výsledné řešení, které jsem použil, je kombinace implementací VirtualPathProvideru a SiteMapProvideru, kde mapa stránek je uložena v SQL Serveru. Fungují všechny prvky ASP.NET. Dokonce takto není potřeba ani cpát žádné ID (typicky článku) jako se to běžně dělá např. u ČT 24. Modul SqlSiteMap má navíc historii, tudíž si pamatuje původní URL stránky, pokud dojde ke změně v názvu stránky, a při dotazu na neaktuální, vrátí HTTP status redirect. Každá URL reprezentovaná objektem třídy SqlSiteMapNode má jedinečné ID (Guid) a TemplateId (Guid) a na základě toho VirtualPathProvider použije příslušnou šablonu (aspx stránku). Generování "pěkného" URL do odkazů jde zase opačným směrem úplně bez problému, protože vím, na jakou URL jsem konkrétní stránku, entitu atd. vypublikoval. je to takové minimální CMSko:) Jediné co jsem zatím nevyřešil, jak se zbavit /default.aspx u každé URL... Řešení mám použito na několika webech např. http://www.galleryworld.net nebo http://www.alda.cz. V případě zájmu mohu poskytnout detaily řešení.
odpovědětodpovědět Gravatar

RE: řešení VirtualPathProvideru + SiteMapProvideru

9.9.2009 14:18:539.9.2009 14:18:53 JirkaJirka ---.net.upc.cz

Zdravím Vás. Mám takový konkrétní dotaz. Mám zřízený hosting u aspone.cz a jestliže si vytvořím subdoménu tak po zadání adresy http://subdomena.domena.cz a načtení webu je výsledná URL ve tvaru http://domena.cz/subdomena/. Chci se Vás zeptat jak nejsnáze docílit toho, aby nedošlo ke změně URL na druhý tvar a URL zůstala dále ve tvaru http://subdomena.domena.cz/stranka.aspx. S .NET teprve začínám tak prosím za polopatické odpovědi předem.

odpovědětodpovědět Gravatar

RE: řešení VirtualPathProvideru + SiteMapProvideru

Obraťte se na svého hostera. To je nějaké jeho nastavení (předpokládám, že za účelem šetření IP adresami), nikoliv standardní chování .NETu.
odpovědětodpovědět Gravatar

Dobré řešení

Vaše navrhované řešení mne zaujalo, jenom nevím přesně jak ho implementovat, můžete mě trochu postrčit?

  • Altairis
  • Nemesis
  • Microsoft MVP
  • IIS
  • ASP.NET