AJAX Control Toolkit: Jak na propojené rozbalovací seznamy?

Jeden z případů, kdy je technologie AJAX k nezaplacení, je výběr z rozsáhlého hodnot, které jsou na sobě závislé. Typicky výběr země a jí příslušejícího regionu (státu, kraje...). Právě na tomto příkladu si ukážeme využití prvku CascadingDropDown, který je součástí ASP.NET Ajax Control Toolkitu.

Poznámka: Popisovaná technologie je stále ve vývoji a zde uvedený kód nemusí být kompatibilní s finální verzí. Veškerý text tohoto článku se vztahuje k ASP.NET Ajax 1.0 RC a AJAX Control Toolkit release 61214 Production.

Jak funguje AJAX v ASP.NET

Technologie AJAX je obecně založena na tom, že klientský skript (JavaScript) si posílá se serverem přes HTTP data formátovaná v XML a podle nich dynamicky modifikuje zobrazenou stránku. Microsoftí implementace využívá toho, že XML data posílaná přes HTTP jsou vlastně webové služby (web services). Javascript vygenerovaný do stránky tedy funguje vlastně jako WS klient a serverová funkcionalita je realizována běžným psaním web services.

Díky tomu se nemusíte učit v podstatě nic nového, protože vše je integrováno do logiky ASP.NET: stačí do stránky umístit odpovídající server controls, napsat normální (okay, skoro normální) webovou službu a máte vyděláno. Nemusíte ani umět JavaScript, vše potřebné si vygeneruje ASP.NET Ajax sám, bez vaší laskavé asistence.

Náš příklad je řešením typického problému v případě globálních webových aplikací. Chcete po uživateli, aby vám prozradil, odkud je. Necháte ho vybrat zemi a poté, na základě toho, co vybral, nějaký bližší region, pokud se na ně vybraná země dělí. Pokud je uživatel z USA, bude tedy vybírat z amerických států. Pokud je z ČR, bude vybírat ze seznamu krajů atd. Obdobným způsobem můžete nechat uživatele vybrat libovolnou kombinaci na sobě závislých parametrů, např. u oblečení velikost, vzorek a barvu.

Data pro vytvoření seznamu zemí a regionů jsem čerpal z normy ISO 3166. Ta mimo jiné definuje pro jednotlivé země dvoupísmenné zkratky. Obvykle se shodují z názvem internetové top level domény (CZ = Česká republika, SK = Slovensko, DE = Německo...), ale ne vždy, např. pro Spojené království platí zkratka GB, nikoliv UK. Kód regionu je jedno- až třípísmenný a zapisuje se oddělen pomlčkou za kódem země. Tedy např. CZ-PR je kód pro Českou republiku, Hlavní město Praha.

Tyto kódy jsou uloženy v databázové tabulce, která obsahuje pole Code (kód země nebo regionu), ParentCode (kód nadřízené země nebo NULL) a Name (název země/regionu). Příslušný databázový soubor je součástí příkladu, najdete v něm také SQL skript pro vytvoření odpovídající tabulky a naplnění obsahem.

Webová služba AjaxUI.asmx

Vlastní načítání dat se děje pomocí webové služby, kterou jsem pojmenoval ~/AjaxUI.asmx. Jedná se o normální web service, jedinou její zvláštností je nutnost odekorovat třídu kromě běžného atributu System.Web.Services.WebService také atributem System.Web.Script.ScriptService. Její metody, které mají být dostupné přes web, pak atributy WebMethod a ScriptMethod.

Pro naše účely jsou důležité metody GetCountries a GetRegions. Ty generují seznam položek rozbalovacího seznamu, v podobě pole tříd AjaxControlToolkit.CascadingDropDownNameValue. Mají dva stringové vstupní parametry: knownCategoryValues a category.

  • knownCategoryValues je seznam dříve vybraných (nadřazených) kategorií. K jeho rozparsování, resp. převedení na StringDictionary, je vhodné použít metodu ParseKnownCategoryValuesString.
  • category je název kategorie, kterou právě vybíráme. Určuje ho nastavení vlastnosti Category u prvku CascadingDropDown.

Důležitá poznámka týkající se názvů parametrů: V .NET Frameworku všeobecně je jedno, jak se jednotlivé parametry metod jmenují, pokud mají odpovídající typ. V případě AJAX webových služeb toto neplatí a názvy parametrů se musejí přesně shodovat, včetmě velkých a malých písmen (a to i v jazyce Visual Basic .NET, který je jinak case insensitive).

Důvodem je skutečnost, že javascriptový web service klient je velmi jednoduchý a v podstatě web services jako takovým vůbec nerozumí, generuje a zpracovává přímo XML dokumenty, aniž by se obtěžoval analýzou WSDL specifikace a podobně. Formát XML dokumentu je dán mimo jiné také jmény předávaných parametrů a závisí v něm na velkých a malých písmenech.

Třída AjaxUI obsahuje pomocnou (privátní statickou) metodu GetItems, která vrací seznam všech položek z tabulky Locations, které mají defginovaný ParentCode (nebo je NULL, pokud je předaný parametr prázdný řetězec nebo null). Vlastní metody GetCountries a GetRegions jsou díky tomu velmi jednoduché:

[WebMethod, ScriptMethod]

public CascadingDropDownNameValue[] GetCountries(string knownCategoryValues, string category) {

  if (category != "Country") return null;      // Invalid category requested

  return GetItems(null);                       // List all countries (locations with no parent)

}

 

[WebMethod, ScriptMethod]

public CascadingDropDownNameValue[] GetRegions(string knownCategoryValues, string category) {

  if (category != "Region") return null;       // Invalid category requested

  StringDictionary kv = CascadingDropDown.ParseKnownCategoryValuesString(knownCategoryValues);

  if (!kv.ContainsKey("Country")) return null; // No parent ctg (country) specified

  return GetItems(kv["Country"]);

}

Metoda GetCountries vrací seznam všech zemí (položek v tabulce, které mají ParentCode rovno NULL). Metoda GetRegions je o něco složitější, protože provádí shora naznačené parsování vybrané nadřazené kategorie. Protože typ požadovaných dat je určen hodnotou parametrucategory, bylo  by možné v obou případech volat jedinou metodu a větvení provést přímo v ní. Pro názornost jsem to napsal tímto rozvláčnějším způsobem.

Privání metoda GetItems načte položky z databáze a vrátí je v podobě pole CascadingDropDownNameValue:

private static CascadingDropDownNameValue[] GetItems(string pc) {

  string cs = ConfigurationManager.ConnectionStrings["Locations"].ConnectionString;

  string sql = "SET ANSI_NULLS OFF;SELECT Code, Name FROM Locations WHERE [email protected] ORDER BY Name";

 

  using (DataTable dt = new DataTable()) {

    // List all locations with specified parent or without parent at all

    using (SqlConnection db = new SqlConnection(cs)) {

      db.Open();

      using (SqlCommand cmd = new SqlCommand(sql, db)) {

        cmd.Parameters.Add("@ParentCode", SqlDbType.VarChar, 10).Value = DBNull.Value;

        if (!string.IsNullOrEmpty(pc)) cmd.Parameters["@ParentCode"].Value = pc;

        using (SqlDataAdapter da = new SqlDataAdapter(cmd)) da.Fill(dt);

      }

    }

 

    if (dt.Rows.Count == 0) {

      // No locations found

      return new CascadingDropDownNameValue[] {new CascadingDropDownNameValue("n/a", pc, true)};

    }

    else {

      // Convert DataTable to list of items

      CascadingDropDownNameValue[] r = new CascadingDropDownNameValue[dt.Rows.Count];

      for (int i = 0; i < dt.Rows.Count; i++) {

        r[i] = new CascadingDropDownNameValue(
                   dt.Rows[i]["Name"] as string,
                   dt.Rows[i]["Code"] as string);

      }

      return r;

    }

  }

}

Volání webové služby

Vlastní ASPX stránka vypadá takto:

<%@Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs"

        Inherits="_Default" EnableEventValidation="false" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"

                      "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

  <title>Untitled Page</title>

</head>

<body>

  <form id="form1" runat="server">

    <asp:ScriptManager ID="ScriptManager1" runat="server"  />

    <div>

      <asp:DropDownList ID="DropDownListCountry" runat="server" />

      <asp:DropDownList ID="DropDownListRegion" runat="server" />

      <asp:Button ID="ButtonSubmit" runat="server" Text="OK"

                  OnClick="ButtonSubmit_Click" />

      <asp:RequiredFieldValidator ID="RequiredFieldValidator1" runat="server"

                                  ErrorMessage="Location is not selected!"

                                  ControlToValidate="DropDownListRegion" />

      <br />

      <asp:Label ID="LabelSelected" runat="server" />

    </div>

    <ajaxToolkit:CascadingDropDown ID="CascadingDropDownCountry" runat="server"

                                   TargetControlID="DropDownListCountry"

                                   Category="Country"

                                   LoadingText="Loading countries..."

                                   PromptText="-- select country --"

                                   ServicePath="~/AjaxUI.asmx"

                                   ServiceMethod="GetCountries" />

    <ajaxToolkit:CascadingDropDown ID="CascadingDropDownRegion" runat="server"

                                   TargetControlID="DropDownListRegion"

                                   ParentControlID="DropDownListCountry"

                                   Category="Region"

                                   LoadingText="Loading regions..."

                                   PromptText="-- select state or region --"

                                   ServicePath="~/AjaxUI.asmx"

                                   ServiceMethod="GetRegions" />

  </form>

</body>

</html>

V první řadě je třeba vypnout event validation, protože položky se do dropdownlistu doplňují dynamicky.

Dále pak je, jako u všech stránek, které využívají ASP.NET Ajax, třeba do stránky umístit prvek ScriptManager.

Poslední zásadní změnu představují dva prvky CascadingDropDown, které se starají o vlastní napojení DropDownListu na webovou službu. Mají tyto důležité vlastnosti (atributy):

  • TargetControlID je ID prvku (DropDownList), který mají plnit.
  • ParentControlID je ID prvku (DropDownList), který patří nadřazené kategorii. Neuvádí se u seznamu na vrcholu hierarchie.
  • Category je název kategorie parametrů, která se váže k danému prvku. Předá se zvolené metodě jako hodnota parametru category.
  • LoadingText je text, který se zobrazí po dobu načítání XML dokumentu z webové služby.
  • PromptText je text výzvy, která se v seznamu zobrazí po načtení dat.
  • ServicePath je cesta k webové službě pro naplnění daty.
  • ServiceMethod je název její metody, která vrátí data.

Závěr

ASP.NET Ajax a Ajax Control Toolkit představují snadno možnost jak psát interaktivnější a pro uživatele pohodlnější aplikace, aniž byste museli vynakládat velké množství úsilí na tvorbu a ladění skriptů pro různé prohlížeče a podobně. Na druhou stranu se mohou stát technologií, která vaši aplikaci zcela pohřbí: nedostatečným výkonem, bezpečnostními dírami a podobně.

Neexistují dobré a špatné technologie, existují pouze technologie vhodně a nevhodně použité. Pokud vás zajímají podrobnosti o ASP.NET Ajaxu a vhodné a nevhodné scénáře použití, přijďte na můj seminář ASP.NET Ajax: Jak ho použít a proč to nedělat, který se koná 16. ledna 2007 v budově Microsoftu v Praze.

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