Stránky pro různá zařízení na jedné adrese v ASP.NET

10. února 2006

S pomocí rozšíření .net Frameworku pro mobilní zařízení můžeme na několika řádcích vytvořit redirektor, který uživatele přesměruje na nejvhodnější verzi stránek pro jeho zařízení. Na rozdíl od podobné funkce v PHP, o které jsme již psali, za nás detekci typu a schopností zařízení totiž provede .net Framework sám. Ukážeme si také, jak zajistit, aby přesměrování bylo korektní s ohledem na SEO.

Přínosem pro zákazníka je skutečně jediná URL, kterou pak zadává do svého desktopového prohlížeče, mobilního telefonu s podporou wapu nebo PDA. Můžeme tak publikovat jedinou krátkou adresu a dokonce se tak i zbavit zastaralého www.

Je potřeba si říct, že zde nepopisujeme jediný způsob, jak nabízet jedinou aplikaci pro vícero zařízení. Další možností může být využití rozšíření System.Web.UI.MobileControls.MobilePage, kdy díky takzvanému adaptivnímu renderingu je aplikace funkční na každém zařízení i přes určitý „spartánský vzhled“ (viz například článek Internetový čas prostřednictvímmobilu v ASP.NET). Takto řešené aplikace můžeme do jisté míry vylepšit zařazením DeviceSpecific šablon. Jiným řešením je vytvořit celou aplikaci v XHTML a nechat kompletně na klientech, jak se s ní poperou – to se může hodit pro méně rozsáhlé aplikace a zejména tam, kde oželíme podporu starších verzí wapu.

Zde popisovaný princip aplikace je analogický aplikaci využívající PHP. Zjistíme typ a schopnosti zařízení a podle toho nastavíme URL pro následné přesměrování na patřičnou verzi stránek.

Systém .net Framework má velmi komplexní rozšíření pro mobilní zařízení v prostoru názvů System.Web.Mobile, dovede rozpoznat velké množství zařízení, a tak se kód aplikace scvrkne jen na zavedení potřebného prostorů názvů, získání údajů o schopnostech zařízení a rozhodovací část switch. Pozornost budeme věnovat korektnímu přesměrování, vestavěná metoda Redirect() totiž vždy odesílá hlavičku 302 Found, což je z hlediska SEO velmi nevhodné, proto musíme použít alternativní způsob. Na závěr si ještě ukážeme, jak .net Framework „naučit“ rozpoznat nové zařízení, které sám o sobě rozpoznat nedovede.

Vycházíme z předpokladu, že výchozí stránka Default.aspx, která zajišťuje detekci a přesměrování, bude v kořeni serveru a na základě detekce přesměruje uživatele do jiného adresáře nebo na jinou stránku. Kód aplikace je opravdu triviální, proto si jej rovnou popíšeme (zdrojový kód ukázek):

<%@Page EnableViewState=“False“ EnableSessionState=“False“ Buffer=“False“ ValidateRequest=“False“ Trace=“False“ Debug=“False“ AutoEventWireUp=“False“ CodeBehind=“StartWebDefault.aspx.cs“ Inherits=“Interval.CZ.Applications.StartWebDefault“ %>

Stránka Default.aspx neposkytuje žádný vizuální výstup. V direktivách Page vidíme zablokované funkce pro udržování stavových informací, které jsou pro naši aplikaci zbytečné a jen by ubíraly systémové prostředky serveru. Z téhož důvodu je zablokována validace požadavků ValidateRequest, vypnuta je i mezipaměť výstupu Buffer.

V obslužném kódu je mimo jiné zaveden potřebný prostor názvů System.Web.Mobile:

namespace Interval.CZ.Applications
{
  using System;
  using System.Web;
  using System.Web.Mobile;
  public abstract class StartWebDefault : System.Web.UI.Page
  {
    private static String DefaultUrl = System.Configuration.ConfigurationSettings.AppSettings[„StartWebDefaultUrl“];
    private static String MobileUrl = System.Configuration.ConfigurationSettings.AppSettings[„StartWebMobileUrl“];
    private static String WapUrl = System.Configuration.ConfigurationSettings.AppSettings[„StartWebWapUrl“];
    private void Page_Load (Object sender, EventArgs e)
    {
      MobileCapabilities currentCapabilities = (MobileCapabilities) Request.Browser;
      String pageUrl = DefaultUrl;
      if (Request.QueryString.Count > 0)
        pageUrl = String.Concat(pageUrl,“?“,Request.QueryString.ToString());
      if ((currentCapabilities.IsMobileDevice && !currentCapabilities.Crawler) || Request.UserAgent.IndexOf(„Windows CE“) > -1)
      {
        switch (currentCapabilities.PreferredRenderingType)
        {
          case „xhtml-basic“:
            pageUrl = WapUrl;
          break;
          case „xhtml-mp“:
            pageUrl = WapUrl;
          break;
          case „html32“:
            if (((currentCapabilities.ScreenCharactersWidth <= 32) || (String.Compare(currentCapabilities.Browser,“pocket pc“,true) == 0) || (String.Compare(currentCapabilities.MobileDeviceModel,“pocket pc“,true) == 0 )) && !(String.Compare(currentCapabilities.Browser,“opera“,true)== 0 && currentCapabilities.Frames))
              pageUrl = MobileUrl;
          break;
          case „chtml10“:
            pageUrl = MobileUrl;
          break;
          case „wml12“:
            pageUrl = WapUrl;
          break;
          case „wml11“:
            pageUrl = WapUrl;
          break;
          default:
          break;
        }
      }
      if (pageUrl != null && pageUrl.Length > 0)
      {
        Response.Clear();
        Response.StatusCode = 301;
        Response.StatusDescription = „Moved Permanently“;
        Response.RedirectLocation = pageUrl;
      }
    }
    override protected void OnInit(EventArgs e)
    {
      InitializeComponent();
      base.OnInit(e);
    }
    private void InitializeComponent()
    {
      this.Load += new System.EventHandler(this.Page_Load);
    }
  }
}

Výkonná část sestává z obsluhy události Page_Load, která nastane při zavádění stránky na serveru. Zde si uložíme získané schopnosti zařízení (MobileCapabilities) z vlastnosti Request.Browser. Dále si nastavíme proměnnou udávající řetězec URL, kam se bude přesměrovávat – výchozí hodnotu nastavíme na adresu naší webové stránky, určené pro běžné prohlížeče osobních počítačů. Z vlastnosti IsMobileDevice určíme, zda se jedná o mobilní zařízení. Pokud ano, podle vlastnosti PreferredRenderingType rozhodneme, jaké schopnosti zařízení má.

Pokud zařízení tvrdí, že umí zobrazit HTML kód verze 3.2, ještě navíc ověříme, zda šířka displeje je větší než 32 znaků nebo zda nejde o zařízení typu Pocket PC. To zjistíme z vlastnosti Browser a u některých zařízení přímo jako rozpoznaný model z vlastnosti MobileDeviceModel. Pokud zařízení vyhoví těmto podmínkám, nastavíme URL na speciální verzi našich stránek vhodnou pro tato zařízení (například počítač s mobilním telefonem typu MDA). Pokud podmínkám nevyhovuje, jde zřejmě o zařízení s dostatečně velkým displejem a proto adresu pro přesměrování měnit nebudeme – ponecháme výchozí a klient bude přesměrován na běžnou webovou verzi. Obdobně potom rozhodneme o přesměrování zařízení, která podporují takzvané compact HTML, WML verze 1.1 a verze 1.2. Zda budou adresy stejné nebo rozdílné záleží na tom, kolik verzí máme připravených.

Oproti popisu v MSDN vrací vlastnost PreferredRenderingType navíc ještě dvě nové hodnoty, xhtml-basic a xhtml-mp. Tyto se týkají především nových mobilních telefonů – naše ukázková aplikace je sice „pošle“ na tutéž verzi jako klasické wapové telefony, každopádně je potřeba se zabývat i těmito hodnotami. Pokud bychom je v aplikaci neošetřili, budou uživatelé takových telefonů posíláni na výchozí stránku určenou pro desktopové prohlížeče. Vzhledem k docela slušné podpoře XHTML u dnešních moderních telefonů se zřejmě hodí připravit verzi dobře fungující jak na těchto telefonech, tak na zařízeních typu PDA, která mají jen o málo větší displej, jinak jsou jejich schopnosti velmi podobné.

Zvláštní pozornost věnujme provedení přesměrování. Jak už jsem zmínil, nabízející se metoda Redirect() je nevhodná – žádným způsobem se nezdaří ji přimět k odeslání stavového kódu 301 a to ani nastavením vlastsnosti StatusCode předem – vždy si pečlivě hlídá odeslání hlavičky 302 Found, což nepotřebujeme. Využijeme tedy alternativní způsob, nastavíme StatusCode na 301, StatusDescription na MovedPermanently a adresu pro přesměrování nastavíme jako hodnotu vlastnosti RedirectLocation. Tímto způsobem bude odeslání hlaviček korektní i v souladu s technikami SEO.

Jelikož aplikace neposkytuje žádný obsah, odesláním hlaviček její úloha končí – a korektně tak skončí i zpracování celé stránky, nemusíme se uchylovat k násilnému ukončení pomocí End(), což by bylo spojeno s vygenerováním výjimky ThreadAbortException a tedy dalším zvyšováním režie aplikace. Nevhodnost tohoto násilného ukončení aplikace byla popsána také v článku Dynamické obrázky v ASP.NET cez HTTPhandler. Ostatně i tento náš redirektor by se dal realizovat pomocí http handleru, ovšem v tomto případě by byla nutná také úprava konfigurace serveru, což není vždy možné.

Problémem mobilních zařízení je, že ne všechna podporují cookies a pokud by je aplikace na zařízení odeslala, může dojít k selhání zobrazení. Bohužel tedy musíme vypnout používání cookies pro session. Rovněž tak je zvláště pro mobilní zařízení vhodné, aby URL, kterou ASP.NET generuje v hlavičce pro přesměrování, byla skutečně absolutní, jak definuje norma RFC. Obojí zajistíme nastavením v souboru Web.config. Nastavení chování mechanismu session je globální (nelze nastavit v sekci location nebo v podřízeném adresáři), proto je lepší, když desktopová verze stránek bude umístěna v podadresáři, pro který je vytvořena zvlášť virtuální aplikace, abychom zde mohli sessions využívat se vším komfortem včetně cookies. Do konfiguračního souboru přidáme také kompletní URL pro jednotlivých verzí našich aplikací.

Potřebné nastavení v konfiguračním souboru Web.config:

<?xml version=“1.0″ encoding=“utf-8″ ?>
<configuration>
  <system.web> 
    <sessionState cookieless=“true“ />
    <httpRuntime useFullyQualifiedRedirectUrl=“true“ />
  </system.web>
  <appSettings>
    <add key=“StartWebDefaultUrl“ value=“http://server.cz/Web/“ />
    <add key=“StartWebMobileUrl“ value=“http://server.cz/Mobile/“ />
    <add key=“StartWebWapUrl“ value=“http://server.cz/Wap/“ />
  </appSettings>
</configuration>

Pokud se stane, že máme zařízení, které .net Framework nerozpozná správně, není problém popsat jeho vlastnosti do zvláštního konfiguračního souboru do sekce browserCaps. V našem příkladu nadefinujeme fiktivní zařízení Interval MDA do souboru Interval.Mda.config:

<browserCaps>
  <use var=“HTTP_USER_AGENT“ />
  <filter>
    <case match=“Mozilla\/4\.0 \(compatible; MSIE 4\.01; Windows CE; PPC; 240×320\)“>
      browser = „Pocket IE“
      cachesAllResponsesWithExpires = „False“
      canInitiateVoiceCall = „True“
      canRenderEmptySelects = „True“
      canSendMail = „True“
      cookies = „True“
      hidesRightAlignedMultiselectScrollbars = „False“
      inputType = „virtualKeyboard“
      isColor = „True“
      isMobileDevice=“True“
      javascript = „True“
      maximumHrefLength = „1024“
      maximumRenderedPageSize = „20000“
      mobileDeviceManufacturer = „Interval.cz“
      mobileDeviceModel = „Interval.cz MDA“
      preferredImageMime = „image/jpeg“
      preferredRenderingMime = „text/html“
      preferredRenderingType = „html32“
      rendersBreaksAfterHtmlLists = „True“
      requiredMetaTagNameValue = „HandheldFriendly“
      requiresAttributeColonSubstitution = „True“
      requiresContentTypeMetaTag = „False“
      requiresDBCSCharacter = „False“
      requiresFullyQualifiedRedirectUrl = „False“
      requiresHtmlAdaptiveErrorReporting = „False“
      requiresLeadingPageBreak = „False“
      requiresNoBreakInFormatting = „False“
      requiresOutputOptimization = „False“
      requiresPostRedirectionHandling = „False“
      requiresUniqueFilePathSuffix = „False“
      requiresUniqueHtmlCheckboxNames = „True“
      screenBitDepth = „24“
      screenCharactersHeight = „17“
      screenCharactersWidth = „32“
      screenPixelsHeight = „320“
      screenPixelsWidth = „240“
      supportsAccessKeyAttribute = „True“
      supportsBodyColor = „True“
      supportsBold = „True“
      supportsCharacterEntityEncoding = „True“
      supportsCss = „True“
      supportsDivAlign = „True“
      supportsDivNoWrap = „False“
      supportsEmptyStringInCookieValue = „True“
      supportsFontColor = „True“
      supportsFontName = „True“
      supportsFontSize = „True“
      supportsImageSubmit = „True“
      supportsIModeSymbols = „False“
      supportsInputIStyle = „False“
      supportsInputMode = „False“
      supportsItalic = „True“
      supportsJPhoneMultiMediaAttributes = „False“
      supportsJPhoneSymbols = „False“
      supportsQueryStringInFormAction = „True“
      supportsRedirectWithCookie = „True“
      supportsSelectMultiple = „True“
      supportsUncheck = „True“
      tables = „True“
      type = „Pocket IE“
    </case>
  </filter>
</browserCaps>

Hodnoty jednotlivých vlastností samozřejmě můžeme stanovit ručně, pro usnadnění se však hodí použít online DeviceProfiler. S jeho pomocí snadno otestujete vlastnosti a možnosti zařízení a nakonec se vytvoří kompletní definice browserCaps.

Zbývá si jen ukázat, jak dáme .net Frameworku vlastnosti našeho zařízení na vědomí. Do kořenového souboru Web.config nebo do hlavního konfiguračního souboru Machine.config přidejte následující definici do sekce system.web a přihrajte vytvořený definiční soubor:

<system.web>
  <browserCaps>
    <file src=“Interval.Mda.config“ />
  </browserCaps>
</system.web>

Podobných definicí je, jak vidno, možné přidat libovolný počet – nicméně se hodí čas od času sledovat updaty, zda není k dispozici update definic mobilních zařízení, abychom vlastní definicí nenosili dříví do lesa.

Popsané řešení je jenom možnou ukázkou, je na každém vývojáři, jak propracované bude mít verze aplikace specifické pro různá zařízení, nebo jak se mu naopak podaří aplikaci udělat univerzální natolik, že se bez tohoto redirektoru obejde. Určitě je také potřeba si připomenout, že nevhodná přesměrování mohou hodně pokazit umístnění ve výsledcích vyhledávačů.

Pozn. red.: Tento článek vyšel poprvé 18. 9. 2003. Původní verze článku a k němu vedené diskuse jsou vám k dispozici v ZIP archivech.

Pozn. red.: Další aktualizované vydání tohoto článku vyšlo 21. 8. 2004. Tato verze článku a k němu vedené diskuse jsou vám také k dispozici v ZIP archivech.

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

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

Předchozí článek ludweek.cz
Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *