V tomto článku si nejprve ukážeme vlastnost HttpRequest.PathInfo. Dále vytvoříme http modul, s pomocí kterého budeme moci jednoduše i ve starších aplikacích vylepšit tvar odkazů a v nových jej můžeme využít jako jeden ze způsobů přípravy „hezky“ vypadajících URL.

Nejprve si popíšeme, co je PathInfo. Jde o vlastnost Requestu, která vrací jakýsi „cancourek“ připojený k URL, k lepšímu pochopení si prohlédněte příklad:

http://server.cz/adresar/podadresar/stranka.aspx/cancourek

Ve výše uvedeném příkladu URL je obsažena i část PathInfo, přičemž její hodnotu tvoří celá část s lomítkem za názvem souboru stránky, tedy /cancourek.

Používání takto definovaných URL není příliš obvyklé, přesto v ASP.NET tato možnost existuje a můžeme ji tedy využít k získávání dalších údajů z požadavku obdobně jako z QueryStringu. Už prostým pohledem na příklad vidíme, že URL s použitím PathInfo může (s ohledem na SEO) vypadat mnohem lépe, než otazníky a ampersandy běžně používaného QueryStringu, viz příklad:

– běžný querystring
/Directory/Shop.aspx?Product=Toaster&Latest=True
– s použitím pathinfo
/Directory/Shop.aspx/Product/Toaster/Latest/True

Předtím, než si ukážeme, jak pomocí Http modulu přetransformujeme údaje v PathInfo na běžný QueryString, je potřeba si uvědomit i některé zápory. Především jde o fakt, že v případě použití URL s PathInfo prohlížeč nepochopí správně, kde se právě nachází bázová adresa, tedy skutečná cesta ke stránce – domnívá se, že celá cesta končí až za posledním lomítkem. Pokud tedy má být taková stránka zobrazena korektně, všechna URL použitá ve stránce musí být uváděna buď absolutně, nebo alespoň relativně od kořene webu, a to nejen odkazy, ale třeba i vložené obrázky, připojené stylopisy a podobně. Dalším problémem mohou být příliš dlouhá URL, nehodí se proto vytvářet velmi košaté a dlouhé PathInfo, jinak se nám může stát, že server na takový dotaz bude odpovídat pouze 400 Bad Request.

Vytváření relativních URL s cestou od kořene webu nám pomůže proměnná udávající prefix právě aktuální cesty od kořene webu, snadno ji pak připojíme ke každému odkazu, obrázku a tam, kde potřebujeme celou relativní cestu:

protected String ContentUrlPrefix = String.Empty;
private void Page_Load(Object sender, EventArgs e)
{
  ContentUrlPrefix = (Request.ApplicationPath.Length > 1) ? String.Concat(Request.ApplicationPath,“/“) : Request.ApplicationPath;
}

Zde si ukazujeme vytvoření prefixu přímo ve stránce, ještě lepší by bylo zveřejnit prefix jako statickou vlastnost třídy pro Global.asax – je možné ji nastavit jednou provždy při startu aplikace v obsluze Application_Start, nechávám na každém vývojáři, který způsob se mu v dané situaci bude hodit lépe nebo který lépe zvládne.

Transformační http modul

Jak jsem sliboval v úvodu, vytvoříme http modul, díky kterému budeme moci části PathInfo přeložit na QueryString, takže aplikace, kterou bude klient vyžadovat, obdrží klasický QueryString. Toho můžeme chytře využít právě u starších aplikací, které tak můžeme minimálním zásahem vylepšit – stačí upravit části, které generují odkazy, zpracování parametrů zůstane beze změny.

Předpokládejme, že klíče i hodnoty QueryStringu prostě naskládáme za sebe oddělené lomítkem a vytvoříme tak PathInfo – viděli jsme už v ukázce URL výše. Úkolem http modulu pak bude rozpoznat, zda požadavek obsahuje PathInfo a pokud ano, tak jednotlivé části oddělené lomítkem složit na QueryString, a to celé metodou RewritePath() předat volané stránce:

using System;
using System.Web;
using System.Text;
namespace Interval.CZ.Common.WebHttpModules
{
  public class PathInfo2QueryString : System.Web.IHttpModule
  {
    public void Init(System.Web.HttpApplication objApplication )
    {
      objApplication.BeginRequest += new EventHandler(this.Application_BeginRequest);
    }
    private void Application_BeginRequest (Object obj, EventArgs e)
    {
      string myAppPath = string.Empty;
      if (obj != null)
      {
        System.Web.HttpApplication objHttpApplication = (System.Web.HttpApplication) obj;
        try
        {
          myAppPath = objHttpApplication.Request.FilePath;
          string RequestedExtension = System.IO.Path.GetExtension(myAppPath);
          if (String.Compare(RequestedExtension,“.aspx“,true) != 0)
            return;
          else
          {
            string originalQueryString = objHttpApplication.Request.QueryString.ToString();
            StringBuilder NewQueryString = new StringBuilder(String.Empty);
            if (objHttpApplication.Request.PathInfo.Length > 1)
            {
              char [] delimiter = („/“).ToCharArray();
              String strParams = objHttpApplication.Request.PathInfo.Substring(1);
              if (strParams.EndsWith(„/“))
                strParams = strParams.Remove(strParams.Length-1,1);
              String [] parameters = strParams.Split(delimiter);
              Int32 pCounter = 0;
              StringBuilder pathInfo2QueryString = new StringBuilder(String.Empty);
              while (pCounter < parameters.Length)
              {
                if (parameters[pCounter].Length > 0)
                {
                  if (pathInfo2QueryString.Length > 1)
                    pathInfo2QueryString.Append(„&“);
                  pathInfo2QueryString.Append(parameters[pCounter] + „=“);
                  pCounter++;
                  if (pCounter < parameters.Length)
                  {
                    if (parameters[pCounter].Length > 0)
                      pathInfo2QueryString.Append(parameters[pCounter]);
                  }
                }
                pCounter++;
              }
              NewQueryString.Append(pathInfo2QueryString);
            }
            if (originalQueryString.Length > 0)
            {
              if (NewQueryString.Length > 1)
                NewQueryString.Append(„&“);
              NewQueryString.Append(originalQueryString);
            }
            objHttpApplication.Context.RewritePath(myAppPath, String.Empty, NewQueryString.ToString());
          }
        }
        catch
        {
          return;
        }
      }
      else
        return;
    }
    public void Dispose ()
    {
      // No unmanaged objects to dispose of
    }
  }
}

Obsluha BeginRequest je doplněna o novou obsluhu události – v té si připravíme cestu k volané aplikaci pomocí FilePath a zjistíme, zda požadavek je veden na stránku s příponou aspx. Pokud jde o jinou příponu, provedeme rovnou return, protože náš modul bude PathInfo překládat na QueryString pouze pro *.aspx stránky (není problém však tuto část upravit dle potřeby).

Má-li se překlad provést, nejprve si odložíme původní QueryString, abychom jej nakonec mohli sloučit s tím, který vznikne z PathInfo. Dále ověříme délku PathInfo, pokud je větší než „1“, pak provádíme složení nového QueryStringu pomocí připraveného StringBuilderu. Řetězec PathInfo bez úvodního a koncového lomítka (pokud jej obsahuje, metodou Remove() jej odstraníme) rozložíme na pole pomocí Split(). Pole poté projdeme v cyklu a postupně přidáváme klíč a jeho hodnotu oddělené znakem „=“, mimo prvního klíče připojujeme také znak „&“ jako oddělovač parametrů nového QueryStringu. Cyklus je napsán tak, aby si dobře poradil i s případy, kdy PathInfo neobsahuje sudý počet položek oddělených lomítkem – tehdy je poslední lichá položka připojena pouze jako klíč bez hodnoty.

Nový řetězec parametrů vzniklý z PathInfo je ještě sloučen s původním QueryStringem, pokud nějaký QueryString byl zadán. Nakonec sestavíme novou URL vedoucí na požadovanou aplikaci i s celým novým QueryStringem a provedením RewritePath() je upravený požadavek předán aplikaci.

Ještě si stručně připomeňme, jak zaregistrovat používání modulu v souboru Web.config:

<configuration>
  <system.web>
    <httpModules>
      <add name=“PathInfo2QueryString“ type=“Interval.CZ.Common.WebHttpModules,PathInfo2QueryString“ />
    </httpModules>
</configuration>

Registrovat použití modulu je možné pouze v kořenovém Web.configu aplikace, což je také důvodem, proč není u tohoto článku funkční ukázka – v souboru ke stažení však naleznete kromě zdrojového kódu modulu i jednoduchou ukázkovou stránku, na které si funkčnost snadno ověříte (zdrojový kód).

Souhrn

Nasazení popsaného způsobu si hodí určitě pro starší aplikace, které je obvykle obtížné upravovat. Je vhodné také pro jednodušší aplikace, které nevyžadují mnoho parametrů v URL. Pro rozsáhlejší aplikace s množstvím parametrů se hodí spíše jako určitý mezipřechod k vytvoření úplně nového řešení – výsledná URL totiž nejsou úplně nejkratší a někdy obsahují i zbytečná části – víme, že SEO friendly URL by neměla být zbytečně dlouhá, jednoduchost řešení a snadnost nasazení však může tyto nevýhody vyvážit alespoň po přechodnou dobu.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Žádný příspěvek v diskuzi

Odpovědět