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.