Monthly Archives: October 2009

ASP.NET AJAX 4: Kliens oldali adatkötés – webszolgáltatáshoz

A cikksorozat előző részében bemutattam, hogyan használhatjuk az ASP.NET AJAX 4 kliens oldali DataView vezérlőjét helyi változókban tárolt adatok adatkötéssel történő megjelenítéséhez. Ebben a részben kiszakadunk a böngészőből és egy webszolgáltatástól kérjük le az adatokat.

A webszolgáltatás elkészítése

A feladat ugyanaz, mint az előző cikkben, országok listáját akarjuk megjeleníteni, ezért készítettem egy Country osztályt, amiben semmi logika nincs, csak összefogja a Name és Capital tulajdonságokat. Mivel ASMX webszolgáltatásból akarom használni, ezért fontos, hogy legyen neki paraméter nélküli konstruktora.

Ezek után készítettem egy CountryWebService.asmx nevű fájlt, benne a CountryService osztállyal. Fontos, hogy mivel AJAX-ból akarjuk meghívni ezt a webszolgáltatást, ezért nem csak a WebSerice, hanem a ScriptService attribútumot is rá kell ragasztanunk.

A webszolgáltatás osztályban készítettem egy privát List<Country> típusú mezőt, ami az adatbázisunkat reprezentálja, ezen fognak futni a kliens lekérdezések.

Eddig tehát semmi extra, itt tartunk:

  [WebService( Namespace = "http://balassy.spaces.live.com/Samples/" )]
  [WebServiceBinding( ConformsTo = WsiProfiles.BasicProfile1_1 )]
  [System.Web.Script.Services.ScriptService]
  public class CountryWebService : System.Web.Services.WebService
  {
    private List<Country> countries;

    public CountryWebService()
    {
        this.countries = new List<Country>()
        {
            new Country( "Austria", "Vienna" ),
            new Country( "Australia", "Canberra" ),
            // És még jó sok további Country...

        };
    }
  }

A WebMethodot úgy írtam meg, hogy paraméterként megkaphassa, hogy az országok neve vagy fővárosa szerint, illetve növekvő vagy csökkenő sorrendben kívánja a kliens megkapni az adatokat:

  [WebMethod]
  public Country[] GetCountries( string orderby, bool asc )
  {
    Func<Country, string> keySelector;        

    switch( orderby )
    {
        case "Name":
            keySelector = new Func<Country, string>( c => c.Name );
            break;
        case "Capital":
            keySelector = new Func<Country, string>( c => c.Capital );
            break;
        default:
            keySelector = new Func<Country, string>( c => c.Name );
            break;
    }

    IEnumerable<Country> result = asc ? 
this.countries.OrderBy( keySelector ) :
this.countries.OrderByDescending( keySelector ); return result.ToArray(); }

Ugyanez WCF-ben

Ha ugyanezt a webszolgáltatást nem ASMX-ben, hanem WCF-ben akarjuk elkészíteni, akkor kicsit többet kell dolgoznunk vele. Ugyanúgy szükségünk lesz egy CountryService osztályra és benne egy GetCountries metódusra, amiket szokás szerint meg kell jelölnünk a ServiceContract és OperationContract attribútumokkal. Továbbá mivel a szolgáltatás Country példányokat ad vissza, arra kellenek a DataContract és DataMember attribútumok.

Mivel a WCF szolgáltatásunkat AJAX-ból akarjuk meghívni, még nem vagyunk készen. Először is kell a szolgáltatás osztályunkra az AspNetCompatibilityRequirements attribútum:

  [AspNetCompatibilityRequirements( 
RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed )]

Továbbá – mint minden WCF szolgáltatáshoz, ehhez is – feltétlenül szükség van egy gigantikus méretű, alig átlátható konfigurációs XML-re a web.config fájlba:

  <system.serviceModel>
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
    <behaviors>
        <endpointBehaviors>
            <behavior name="CountryServiceAspNetAjaxBehavior">
                <enableWebScript />
            </behavior>
        </endpointBehaviors>
        <serviceBehaviors>
            <behavior name="CountryServiceBehavior">
                <serviceMetadata httpGetEnabled="true" />
                <serviceDebug includeExceptionDetailInFaults="true" />
            </behavior>
        </serviceBehaviors>
    </behaviors>
    <services>
        <service behaviorConfiguration="CountryServiceBehavior" name="CountryService">
            <endpoint address="" binding="webHttpBinding" 
contract="CountryService"
behaviorConfiguration="CountryServiceAspNetAjaxBehavior" /> </service> </services> </system.serviceModel>

A sok beállítás közül a két legfontosabb a webHttpBinding és az enableWebScript.

Ha eddig készen vagyunk, akkor a kliensnek már mindegy, hogy milyen technológia dolgozik szerver oldalon.

Kliens oldali adatkötés deklaratívan

Az adatkötéshez kliens oldalon megint csak egy egyszerű HTML fájlt készítettem, benne script blokkokkal pedig betöltöttem az ASP.NET AJAX 4 JavaScript fájljait az ajax.microsoft.com CDN-ről. Az országok felsorolását most is ul-li listában végezzük el a kliens oldali DataView vezérlő segítségével, ami deklaratívan mindössze ennyi:

  <ul
      class="sys-template"
      sys:attach="dv"
      dv:autofetch="true"
      dv:dataprovider="Services/CountryWebService.asmx"
      dv:fetchoperation="GetCountries"
      dv:fetchparameters="{{ { orderby: 'Name', asc: true } }}" >
      <li>
          {{ Name }} ({{ Capital }}) 
      </li>
  </ul>

dv:dataprovider

A DataView dataProvider tulajdonságával adjuk meg, hogy honnan jönnek az adatok. Ez lehet egy sima JSONos webszolgáltatás URI, egy Sys.Net.WebServiceProxy példány vagy bármilyen osztály, ami implementálja a Sys.Data.IDataProvider interfészt, azaz van fetchData metódusa. Az utóbbira példa a Sys.Data.AdoNetServiceProxy osztály, amivel ADO.NET Data Services (Astoria) szolgáltatásokat hívhatunk meg, a legegyszerűbb esetben pedig közvetlenül rámutathatunk az .asmx vagy az .svc fájlra.

dv:fetchoperation

A DataView a fetchOperation tulajdonságban várja annak a metódusnak a nevét, amit a webszolgáltatáson meg akarunk hívni. Ide lehet query stringet is írni, ami ADO.NET Data Services (Astoria) esetén további lehetőségeket ad szűrésre, rendezésre, lapozásra.

dv:fetchparameters

A fetchParameters tulajdonságon keresztül paraméterezhetjük fel a meghívandó webmetódusunkat. Egyetlen JSON “dictionary”-t kell csak összeállítanunk.

dv:autofetch

Az autoFetch tulajdonság true-ra állításával adhatjuk meg, hogy a DataView az oldal betöltődésekor azonnal forduljon az adatforráshoz és töltse fel magát adatokkal. Ha ezt false-ra állítjuk, akkor nekünk kell meghívni a fetchData metódust, például így (feltételezve, hogy a fenti ul elemet elláttuk egy id=”list” attribútummal):

  $find('list').fetchData();

Kliens oldali adatkötés kódból

Természetesen itt is van lehetőség arra, hogy a HTML markupot és az adatkötést szétválasszuk, tehát a teljes adatkötést tisztán kódból végezzük el. Csupaszítsuk le a markupot (az előző cikkben bemutattam, hogyan lehet a {{}} elemektől megszabadulni, itt most csak az adatforrás kezelésére koncentrálok, ezért ezeket most meghagytam):

  <ul
    id="list"
    class="sys-template" >
    <li>
        {{ Name }} ({{ Capital }}) 
    </li>
  </ul>

Az adatkötést pedig az oldal inicializálásakor végezzük el, így:

  <script type="text/javascript">
    Sys.Application.add_init(appInit);
    function appInit()
    {
        $create(
            Sys.UI.DataView,
            {
              autoFetch: false,    // Hogy legyen lehetőségünk még inicializálni
              dataProvider: "Services/CountryService.svc",
              fetchOperation: "GetCountries",
              fetchParameters: { orderby: 'Name', asc: true }
            },
            null,
            null,
            $get("list")
        );
        // Ide jöhetne további inicializálás...
        $find('list').fetchData();
    }
  </script>

Ennyi, ki lehet próbálni, működni fog.

Rendezés

Miután rátettük a kezünket a fetchParameters tulajdonságra, semmi akadálya, hogy a felhasználó határozza meg, milyen sorrendben akarja látni az adatokat. Hogy egyszerűbb legyen az életünk, a rendezés adatait és a DataView vezérlőt kiemelhetjük globális fetchParams és dv változókba:

  var fetchParams = { orderby: 'Name', asc: true }
  var dv;
  Sys.Application.add_init(appInit);
  function appInit()
  {
      $create
( Sys.UI.DataView, { autoFetch: false,
dataProvider: "Services/CountryService.svc", fetchOperation: "GetCountries", fetchParameters: fetchParams }, null, null, $get("list") );
      dv = $find('list');
      dv.fetchData();
  }

Kellene még két metódus, amellyel gyorsan lehet rendezési szempontot és irányt váltani és amelyek azonnal frissítik a DataView vezérlőt:

  function sortBy(col)
  {
    fetchParams.orderby = col;
    dv.fetchData();
  }

  function sortAsc(dir)
  {
    fetchParams.asc = dir;
    dv.fetchData();
  }

Végül már csak UI elemekre van szükség ezeknek a függvényeknek a meghívásához:

  <a href="#" onclick="sortBy('Name')">Név szerint</a> 
  <a href="#" onclick="sortBy('Capital')">Főváros szerint</a> 
  | 
  <a href="#" onclick="sortAsc(true)">A-Z</a> 
  <a href="#" onclick="sortAsc(false)">Z-A</a>

Rakjuk össze mindezt egy oldalra és a felhasználó boldogan kattintgathat a linkekre, a háttérben a megfelelő sorrendben fognak letöltődni az adatok és azonnal meg is jelennek a böngészőben – természetesen AJAXosan frissítve az oldalnak csak az érintett részét.

A szép az egészben, hogy a tudomány nem áll meg itt: a rendszer képes arra is, hogy észrevegye, ha egy változó értéke megváltozik és automatikusan frissítse az értéket a felhasználói felületen. Folyt. köv.

A cikkhez tartozó forráskód letölthető innen.

Advertisements

ASP.NET AJAX 4: Kliens oldali adatkötés – alapok

Több, mint 2 éve, hogy először írtam lelkendezve a kliens oldali deklaratív adatkötésről. Akkor mindez az ASP.NET Futures részeként, mintegy előzetesként volt elérhető, és ahogy a 2007-es Web Konferencián be is mutattam, az XML-Script volt a fő csapásirány. Aztán tavaly nyáron jött a hír, hogy az XML-Script megy a kukába, és az ASP.NET AJAX-ban egy teljesen új megvalósítással fogunk találkozni.

Az ASP.NET AJAX 4.0 előzetesét háromféleképpen próbálhatjuk ki:

  • Visual Studio 2010: A fejlesztőeszköz és vele együtt a Framework jelen pillanatban béta 1 verzióban van és szabadon letölthető innen. Valószínűleg a novemberi PDC és TechEd idején lesz belőle béta 2 és majd valamikor jövő tavasszal RTM. A jó hír az, hogy simán fel lehet telepíteni VS 2008 és .NET 3.5 mellé, vígan futnak side-by-side. Persze ahogy mindig, ezt most sem célszerű production gépen kipróbálni, mert még nem tudjuk, hogy a béta 2-t és az RTM-t lehet-e majd olyan gépre telepíteni, amin volt béta 1, illetve, hogy a béta 1 eltávolítása simán fog-e menni. A másik saját tapasztalatom, hogy kiválóan használható remote desktopon keresztül is (ellentétben az egyik szakfolyóiratban megjelent WPF ostorozó cikkel).
  • ASP.NET AJAX Preview 5: Az ASP.NET csapat folyamatosan közzéteszi preview változatban az AJAX Library-t a CodePlexen. Az aktuális Preview 5 változat szabadon letölthető és kipróbálható VS 2008-on is.
  • Microsoft AJAX CDN: A Microsoft nemrég jelentette be, hogy az ASP.NET AJAX és jQuery Library-ket kipakolja Content Delivery Networkökre. Ez azt jelenti, hogy nem kell a JavaScript fájokat a saját webszerverünkön hosztolnunk, hanem elég megadnunk egy Microsoftos webkiszolgáló URL-jét és a böngésző onnan fogja letölteni a fájlokat. Ráadásul mindezt úgy, hogy a háttérben valójában a földrajzilag hozzánk legközelebbi webkiszolgálóval kommunikálunk, de az is lehet, hogy már egy másik webhely használta ezeket a fájlokat így megtalálhatóak a böngészőnk gyorsítótárában. A pontos URL-eket itt lehet megtalálni és érdemes kipróbálni, hogy egy tracert ajax.microsoft.com honnan nézve hány hop.

Mivel kliens oldali adatkötésről van szó, nincs szükségünk másra, csak az ASP.NET AJAX Library-höz tartozó JavaScript fájlokra. Az új verzió újdonsága, hogy szétszedték ezeket a script fájlokat és publikálták, hogy hogyan függnek egymástól:

ASP.NET AJAX Library szkript fájlok függőségei

Az aktuális bétában nincs ennyi fájl, csak core, AdoNet, DataContext, Templates és WebForms van. Ha csak az adatkötést használjuk, akkor a MicrosoftAjax.js és a MicrosoftAjaxTemplates.js fájlokra kell hivatkoznunk. Fontos látni, hogy ezek egyáltalán nem kapcsolódnak a szerver oldali ASP.NET-hez, ezért a technológia használható más környezetben is.

Ha ez kész, akkor létrehozhatunk a kódunkban egy pageLoad függvényt, amit a rendszer automatikusan meghív, miután az AJAX Library betöltődött. Szintén újdonság, hogy már nem kell meghívni a Sys.Application.initialize metódust, hogy ez megtörténjen:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>ASP.NET AJAX 4 (Preview 5) példakód (Balássy György)</title>
        <script type="text/javascript" 
src="http://ajax.Microsoft.com/ajax/beta/0909/MicrosoftAjax.js"></script> <script type="text/javascript"
src="http://ajax.Microsoft.com/ajax/beta/0909/MicrosoftAjaxTemplates.js"></script> <script type="text/javascript"> function pageLoad() { alert('Ez már ASP.NET AJAX 4!'); } </script> </head> <body> </body> </html>

Példánkban az adatforrás legyen egy objektum tömb országokkal és városokkal, amit egy felsorolt listában szeretnénk megjeleníteni:

  var countries = [
    { Name: 'Austria', Code: 'at', Capital: 'Vienna' },
    { Name: 'Australia', Code: 'au', Capital: 'Canberra' },
    { Name: 'Brasil', Code: 'br', Capital: 'Brasília' },
    { Name: 'Croatia', Code: 'cr', Capital: 'Zagreb' },
    { Name: 'Spain', Code: 'es', Capital: 'Madrid' },
    { Name: 'Finland', Code: 'fi', Capital: 'Helsinki' },
    { Name: 'Greece', Code: 'gr', Capital: 'Athens' },
    { Name: 'Hungary', Code: 'hu', Capital: 'Budapest' },
    { Name: 'Sweden', Code: 'se', Capital: 'Stockholm' },
    { Name: 'Slovakia', Code: 'sk', Capital: 'Bratislava' },
    { Name: 'United Kingdom', Code: 'uk', Capital: 'London' },
    { Name: 'United States', Code: 'us', Capital: 'Washington, D.C.' }
  ];

Adatkötés deklaratívan

Az adatkötés deklaratív leírásához újabb névtereket kell bevezetnünk a body elemben:

  <body xmlns:sys="javascript:Sys" xmlns:dv="javascript:Sys.UI.DataView">

Innen már csak egy lépés, hogy meg is jelenjenek az adatok:

  <ul
    class="sys-template"
    sys:attach="dv"
    dv:data="{{ countries }}" >
    <li>
        {{ Name }} ({{ Capital }})
    </li>
  </ul>

Kicsit (?) szokatlan a szintakszis, de nézzük sorban, mi ebben az extra!

class=”sys-template”

Ezzel kell megmondanunk a template engine-nek, hogy ezt a HTML DOM elemet adatkötéshez sablonként használjuk. Fontos, hogy ennek a CSS osztálynak léteznie kell és tartalmaznia kell a display: none; beállítást. (Ez egyébként okozhat problémát, például a Google Maps vezérlő nem szereti, ha láthatatlan állapotban akarjuk inicializálni.) Egyes források szerint a név bármi lehet, de tapasztalatom szerint nem működik minden, ha nem sys-template a neve.

sys:attach=”dv”

A sys és dv névtereket a body elemben definiáltuk és az AJAX Library névterére és osztályára mutatnak. A sys:attach segítségével egy létező osztály, jelen esetben a Sys.UI.DataView osztályt kapcsoljuk az aktuális HTML elemhez. Az adatkötés kulcsa tehát a kliens oldali új DataView vezérlő.

dv:data = “{{ countries }}”

Mivel a dv névteret a body elemben definiáltuk, ezzel a szintakszissal állíthatjuk be a DataView osztály tulajdonságait. A data tulajdonságot akkor használjuk, ha egy rendelkezésre álló JavaScript objektumhoz vagy tömb változóhoz szeretnénk kötni, jelen esetben a korábban létrehozott countries tömb a kedvezményezett. A DataView képes magát akár webszolgáltatásból is feltölteni, aminek beállításához a dataprovider, fetchoperation, fetchparameters, httpverb és autofetch tulajdonságokat használhatjuk.

{{ Name }} és {{ Capital }}

A {{ }} blokkban tetszőleges JavaScript kifejezés állhat, ami az adatkötés során minden egyes rekordnál ki fog értékelődni. Ezen belül hivatkozhatunk az aktuális rekord tulajdonságaira, mint a fenti példában a countries tömb egyes elemeinek Name és Capital mezőire.

Adatkötés kódból

A fenti kódrészletet joggal érheti az a vád, hogy kusza, keveredik benne a logika és a megjelenítés. Szerencsére van lehetőség az adatkötést leválasztani a HTML markupról, ez esetben viszont kódhoz kell fordulnunk. A vezérlők felkonfigurálását az application init eseménykor célszerű elvégezni, ezért fel kell rá iratkoznunk:

  Sys.Application.add_init(appInit);

Ebben az eseménykezelőben létrehozhatjuk a DataView vezérlőnket az AJAX Library $create(type, properties, events, references, element) metódusával és ráhúzhatjuk a HTML markupban id=”list” attribútummal ellátott elemre:

  function appInit()
  {
    var dv = $create(
        Sys.UI.DataView,
        {
          data: countries                    
        },
        {
          itemRendered: onItemRendered
        },
        null,
        $get("list")
    );
  }

A korábbiakhoz képest új, hogy fel kell iratkoznunk az itemRendered eseményre is, mert ott tudjuk az egyes rekordokhoz tartozó adatkötéseket konfigurálni. Ha esetleg már létezne ilyen DataView vezérlőnk és csak az adatforrását kívánjuk állítani, akkor azt legegyszerűbben így tehetjük meg:

  $find('list').set_data(countries);

Ezek után a markup sokkal letisztultabb, még a body tagről is lekerült a dv névtér:

  <ul
    id="list"
    class="sys-template">
    <li>
        <span sys:key="Name"></span>
        (<span sys:key="Capital"></span>)
    </li>
  </ul>

A sys:key attribútumok célja, hogy az egyes rekordok renderelésekor megtaláljuk a helyet, ahol az adatokat megjelenítjük. Ehhez Sys.Binding objektumokat hozhatunk létre az onItemRendered eseménykezelőben:

  function onItemRendered(sender, e)
  {
    var context = e.get_itemContext();
    var dataItem = e.get_dataItem();

    $create(
        Sys.Binding,
        {
            source: dataItem,
            path: "Name",
            target: context.keys.Name,
            targetProperty: "innerHTML"
        }
    );

    $create(
        Sys.Binding,
        {
            source: dataItem,
            path: "Capital",
            target: context.keys.Capital,
            targetProperty: "innerHTML"
        }
    );                
  }

Természetesen arra is van lehetőség, hogy teljesen megszabaduljunk a sys: névtértől. Ebben az esetben id attribútum kell minden span elemre:

    <body>
        <ul
            id="list"
            class="sys-template">
            <li>
                <span id="Name"></span>
                (<span id="Capital"></span>)
            </li>
        </ul>
    </body>

A Sys.Binding objektumok létrehozását pedig úgy kell átírnunk, hogy az id attribútum alapján találjuk meg az adatok helyét:

  $create(
    Sys.Binding,
    {
        source: dataItem,
        path: "Capital",
        target: $get('Capital', context),
        targetProperty: "innerHTML"
    }
  );    

Ez az adatkötés legegyszerűbb formája: egyirányú, lokális változóból dolgozik, de nem veszi észre annak a változásait. Mindhárom irányba lehetne bővíteni a példát, a rendszer képes kétirányú adatkötésre, a DataView pedig fel tudja magát tölteni akár egy WCF szolgáltatás meghívásával és a változásokat automatikusan meg is tudja jeleníteni, sőt akár a szerverre is vissza tudja frissíteni. De ezekről majd legközelebb…

A cikkhez tartozó forráskód letölthető innen és innen.