Archívum

Archive for 2008. december

ADO.NET Data Services és a Silverlight 2.0

december 2, 2008 Hozzászólás

Legutóbb ott hagytuk abba, hogy megismerkedtünk az ADO.NET Data Services-zel, áttekintettük, hogy miként lehet lekérdezéseket megfogalmazni az URL segítségével, valamint hogyan lehet a szolgáltatást jobban testreszabni interceptorok, illetve service operation-ök segítségével. Most azt tekintjük át, hogy miként tudjuk Silverlight 2.0-ban használni az ADO.NET DS-t, hogyan lehet CRUD műveleteket kezdeményezni.

Referencia felvétele a szolgáltatásra

Az előző projektben elkészített Data Service tökéletes lesz, mindössze az interceptorokat távolítottam el belőle.

Egy új projektet kell felvennünk a solution-be, mégpedig egy Silverlight Application-t kell készítenünk SilverlightClient néven. A studio megkérdezi, hogy melyik website legyen az alkalmazás host-ja, (netán egy újat szeretnénk hozzárendelni) én most itt a meglévő site-omat (ahol a data servicem is van) választanám.

Ha szeretnénk használni a szolgáltatást, először referenciát kell felvennünk rá. Ez a lépés autómatikusan legenerálja azt a proxy osztályt, melyen keresztül elérhetem a szolgáltatásomat. SilverlightClient-en jobb klikk, Add Service Reference… A Discover gombra klikkelve rögtön meg is találja a solutionban lévő szolgáltatást.

(Ha vmilyen okból kapnánk egy error message-et az OK-ra klikkelést követően, amire sajnos van esély, akkor a felvett referencián egy jobb klikket kell nyomni és az Update Reference-t kell választani. Megjavul 🙂 )

Na nézzük mi minden történt. A Solution Explorer-en klikkeljünk a show all files-ra, és nyissuk ki a DataServiceReference-t! 

Nocsak! Service.edmx?? Entity Framework kliens oldalon? Ha belenézük, bizony úgy néz ki, mintha a Conceptual Models lenne benne! A reference.cs-t kinyitva bizony meglepődök: Products, Categories entitás osztályok, valamint NorthwindEntities, ami a DataServiceContext osztályból származik. Gondolom ezen a ponton már ti is rájöttetek miről van szó… IGEN! Az adatmodellem (egy webszerveren van) kipublikáltam egy szolgáltatáson keresztül, amelyhez tartozó kliensoldali proxy gyakorlatilag kliensoldali változáskövetést biztosít számomra!!! Bizony… szerveren futó adatmodellhez, kliensoldali változáskövetés, miközben a kettő egy webszolgáltatással kommunikál, transzparens módon. Vegyük hát használatba!

Lekérdezések építése Linq To ADO.NET Data Services segítségével

Nem vicc! Nem csak a URL alapú lekérdezéseket konkatenálhatjuk, hanem használhatjuk a Linq To Ado.Net DS provider-t, ami ezt a munkát elvégzi helyettünk! Gyorsan felvettem egy ListBoxot a felhasználói felületemre, hogy az eredményt megjeleníthessem!

Most már csak a lekérdezést kell megírni, irány a Page.xaml.cs Page() konstruktora. Először is a Proxy osztályt kell megpéldányosítani, a proxy konstruktora egy URI objektumot vár, amely a szolgáltatás címét tartalmazza.

 
(Ez a NorthwindEntities NEM az entity framework által generált osztály, ez a proxy-nk!)

Kérdezzük le az összes terméket, és jelenítsük meg a listboxunkban. Íme az első próbálkozás:

Az arcunkba egy óriási Exception-t kapunk! Jobban megvizsgálva a kivételt, annyit sikerül kimazsolázni, hogy bizony ez a művelet nem támogatott! Na de milyen művelet? Bizony Silverlightból nem tudjuk a data service-ünket szinkron módon meghívni, csak is aszinkron műveletek támogatottak! A query-nek viszont nincs semilyen aszinkron művelete, ami segíthetne a lekérdezésben, ezért egy külső (ős) osztályt kell segítségül hívni! Ez pedig a DataServiceQuery<T>:

Szóval a mezei query objektumunkat átcastolva a DataServiceQuery<Products>-ra sikerül szert tennünk egy BeginExecute metódusra. Az aszinkron műveleteknél megszokott módon, szükség van egy Callback függvényre, ami meghívásra kerül, ha az aszinkron művelet véget ér, második paraméterként pedig átadjuk az objektumot, amin a változás beáll.

Íme a callback függvényünk! Egy paramétert fogad, ez pedig az IAsyncResult típusú változónk. A korábban átpasszolt dsQuery-hez itt a result.AsyncState objektummal férünk hozzá (ez object), de még át kell castolni a megfelelő típusra!
A dsQuery-n meghívhatjuk az EndExecute(IAsyncResult) metódust, ezzel lezárva az aszinkron műveletet. Az EndExecute visszatérési értéke lesz a mi eredményhalmazunk. Ezt már hozzáköthetjük az listboxunk ItemsSource tulajdonságához. Az erdmény a következő:

Picit testreszabva a megjelenést az alábbi kóddal (DataTemplate)…

…az alábbi eredményt kapjuk:

Szóval lekérdezéseket írni nem bonyolult, futtatni sem vészes, csak szem előtt kell tartanunk, hogy bizony aszinkron kommunikáció mehet csak, ha Silverlight 2.0 a kliensünk.

Nézzünk egy bonyolultabb lekérdezést!

A lekérdezés probléma nélkül lefut, az eredmény az alábbi ábrán látható.

Mit csinál a háttérban a Linq To ADO.NET Data Services provider? Természetesen összerakja az URL alapú lekérdezést. Íme az előző lekérdezés debug módban.

Ez nagyon kellemes, ugyanakkor figyelembe kell vennünk ezt a tényt, minden egyes query írásánál. Számos korábban megszokott linq művelet nem fog működni, ugyanis csak azok a műveletek támogatottak, amit át lehet írni URL alapú lekérdezésbe.
(így pl a select new {p.ProductID, p.ProductName} itt nem is működhetne! Más kérdés, hogy miután az adat lejött és a memóriába van becachelve, utánna már lehet transzformálni, szűrni)

Új elem felvétele

Elhelyeztem egy gombot a felhasználó felületen, amelynek eseménykezelőjében veszek fel egy új terméket. (A NorthwindEntities-t közben kiraktam a Page osztályba privát változóként)

Ugye milyen kellemes az a Products.CreateProducts() metódus? (végre nem az adatbázisból kell kitúrni, hogy mi a null és mi a nem null, ha demózok 🙂 ) A dc-nek van egy AddToProducts metódusa, ami felveszi az új product-unkat a Products gyűjteménybe!
Hát látjuk, hogy szinkron SaveChanges-nek nyoma sincs (WPF, Winform, ConsoleApp-nál van), de ez nem lep meg minket, rutinosan a BeginSaveChanges-t keressük. Akár csak az előbb itt is kell egy callback függvény, illetve a változás az adatkontextuson áll be, tehát a második paraméter ő lehet.

Az SavingCompleted metódusban olvashatjuk ki a szolgáltatás által küldött választ, hogy miként is sikerült ezt a beszúrás dolog.

Jól megvizsgálva a response belsejében feltűnik, hogy az imént beszúrt elem a "107-es" ID-val sikeres volt (hiába adtam én meg 1-et id-nak, autoinkremens, ennek örülök:) ).

Viszont a listában (ha a szűréseket kizárjuk) nem jelenik meg az új termék! Irány a Reference.cs, hogy megnézzük miért. Látjuk, hogy a Products, Categories gyújtemények DataServiceQuery<T> típusúak. Ha megnézzük ennek a típusnak a definícióját látjuk hogy nem valósítja meg az INotifyCollectionChanged interface-t. Marad a kézi updatelés, újralekérdezés, valamilyen mechanikus frissítés, esetleg vmilyen wrapper class az egész köré. 😦

Ha netán valami miatt nem jönne össze a beszúrás (pl primarykey egyediségének megsértése, ahol nem autoinkremens) akkor kapunk egy szép DataServiceRequestException-t, ami az EndSaveChanges hívásnál érkezik meg (aszinkron esetben), és hát nekem nem sikerült kibogarászni belőle a hiba jelleggét. Na szóval érezni, hogy a hibakezelésre (konkurrencia problémák stb..) egy külön fejezetet szentelhetünk. (meg is tesszük majd 🙂 )

Meglévő elem módosítása.

Változáskövetés tudjuk, hogy van, property-t módosítunk. Az UpdateObject metódus, modified állapotba billenti az entitás példányunk, majd kiadjuk a BeginSaveChanges hívást (lásd korábban), ami a módosult entitásokat felszinkronizálja. A korábban készítet SavingCompleted callback függvény jó is lesz. Ez kézenfekvő…

Azonban a listában nem változik az adott property neve! Ha ismét átmegyünk a reference.cs-be láthatjuk, hogy a Products osztály és a Categories osztály nem valósítja meg az INotifyPropertyChanged interface-t sem. Szóval nem csak a listában történő változásokat nem fogjuk észrevenni, de az objektumok propertyjeiben beállt változásokat sem 😦

Erre workaround lehet (az újra lekérdezésen kívül), ha megnézzük, hogy az egyes propertyk setterében a partial OnXXXChanged metódus meghívásra kerül. Tehát készítünk egy saját Partial Product osztályt (kiegészítjük a meglevőt), implementáltatjuk vele az INotifyPropertyChanged interface-t, majd az eseményt ki is váltjuk.

Ezzel az apró kiegészítéssel a ProductName property változásakor jelentést kapunk az eseményről, így a felhasználó felület is kiértesül, és frissíti az elemet!

Meglévő elem törlése.

Kiválasztjuk a törlendő elemet (linq to objects), majd a DeleteObject() metódus segítségével töröljük. A változások mentése a megszokott mederben folyik! Sajnos a felhasználói felület nem értesül a változásról, ezt ismét nekünk kell elintézni.

Remélem ízelítőnek ennyi elegendő volt. 🙂 A folytatás(ok)ban batch updateről, konkurrencia kezelésről, hitelesítésről, relációk bejárásáról, kapcsolódó entitások betöltéséről lesz szó.

Kategóriák:Uncategorized

ASP.NET Dynamic Data Intro (2.)

december 1, 2008 Hozzászólás

Legutóljára ott hagytuk abba, hogy valahogy jó lenne az adatok validálását is jobban kézben tartani, valamint szeretnénk egyes táblák megjelenítését egyénire szabni. Ez a bejegyzés most az előbbi, validációs témába nyújt egy rövid betekintést.

ADO.NET Dynamic Data  – Bemeneti adatok validálása

Az előző alkalmazás mentén tovább a haladva, ha futtatjuk a projektet, és átnavigálunk a Customers táblához, egy sort szerkesztve láthatjuk, hogy megtehető, hogy a ContactName mezőt üresen hagyjuk és úgy updatelünk.

 
Validátor vezérlő (jelenesetben RequiredFieldValidator) nem kapcsolódik hozzá, mert az adatmodell semmilyen kényszert nem definiál az adott mezőhöz. Ugyanezt elkövetve a CompanyName mezővel a következő eredményt kapjuk:

 
Azaz a validátorok elhelyezése az adatmodellen definiált kényszerekhez köthető. A CompanyName property a Customers osztályban a következő attribútummal van ellátva: [global::System.Data.Objects.DataClasses.EdmScalarPropertyAttribute(IsNullable=false)]
Felmerül a kérdés, hogy az adatmodell megváltoztatásán túl lehet-e egyéb módszereket alkalmazni a validálás testreszabására. Gondolok itt a validáció típusára, a szöveg lokalizációjára, megváltoztatására, vagy akár eddig nem validált mezők validálására. A válasz természetesen egyértelmű igen! A validációt több szinten is végezhetjük:

    1. Input szinten
      1. Metaadatokkal
    2. Adatmodell szinten
      1. Property szinten
      2. Entitás szinten
      3. Contextus szinten

Validálás metaadatokkal input szinten

Készítsünk egy metaadat osztályt a Customers entitáshoz is, ahol konfigurálhatjuk a validációs mechanizmust.

1. RequiredFieldValidator

A ContextName mező kitöltése a Required attribútumnak köszönhetően kötelező, valamint a default "The ContactName field is requied" helyett  "A ContactName mezőt ki kell töltenie" üzenetet kapjuk az ErrorMessage beállításának köszönhetően.

2. RangeValidator

A Products osztályban a UnitsInStock property értéke csak 0 és 1000 közé eshet!

3. StringLenghtValidator

Jól jöhet, ha definiálhatjuk, hogy maximum hány karakter hosszú stringet rendelhetünk egy értékhez. Különösen kellemes, ha a Dynamic Data ennek megfelelően a TextBoxunk MaxLength property-jét is rögtön beállítja, azaz nem tudok X karakternél többet beírni a szövegdobozba. Ezt a StringLengthAttribute segítségével érhetjük el.

Jelen esetben az ErrorMessage még felesleges is hiszen, eleve nem tudunk 15 karakternél többet bírni a Country-hoz tartozó TextBox-ba! Erre a bizonyítékot a kirenderelt HTML-ben találhatjuk meg:

4. RegularExpressionValidator

A legrugalmasabb beépített validációs mechanizmus talán a reguláris kifejezés alapján történő validáció. Az alábbi példában a Shippers táblában található Phone mezőhöz füzök egy fix telefonszám formátumot (xxx) xxx-xxxx formában. Segítségemre a RegularExpressionAttribute siet.

A kimeneten pedig jól látszik az eredmény:

Validálás adatmodell szintjén

Időnként ennél is rugalmasabb validációra van szükség. A saját validációs logikát elhelyzetjük az adatmodellben is, akár több szinten.

1. Validálás Property szinten

Ha mindössze egyetlen property-t szeretnénk validálni megtehetjük a property-hez tartozó, helyesebben fogalmazva, a property setterében meghívásra kerülő parciális metódusokban. Vessünk egy pillantást a Customers osztály ContactTitle mezőjére:

Látjuk a setter-ben, hogy a _ContactTitle privát változó beállítása előtt meghívásra kerül az OnContactTitleChanging(string) metódus. Hol van ennek a metódusnak a definíciója? Néhány sorral lentebb látható: partial void OnContactTitleChanging(string value). Azaz a prototípus definiált, de implementálva nincs. Mivel partialként van megjelölve a meghívása nem okoz problémát, lehetőséget kapunk, hogy később implementáljuk ezt a metódust. Ezt tesszük meg most!

Ha a custom logikánk (amit egy RequiredAttribute-tal is kiválthattam volna, mindenki saját fantáziájára bízom, hogy milyen custom logikát alkalmazna itt) szerint érvénytelen az input, akkor egy ValidatonException-t eldobunk. Na jó, de ki kapja el? A válasz az adott pagetemplate-ben található!

Jól látható, hogy egy DynamicValidator helyezkedik el az Edit.aspx-ünkben, amely a DetailsView1-et igyekszik validálni. Na ő az, aki elkapja ezeket a ValidationException-öket és megfelelően kezeli. (Ismét valami, ami kész van, különösebb kódolás nélkül)

2. Validálás Entitás szinten (Linq To Sql)

Ha netán nem egyetlen property, hanem egy egész entitás szintjén szeretnénk validálni, erre is van lehetőség, mégpedig az
OnValidate(ChangeAction) parciáls metódus keretében (Linq To Sql adatmodellnél).

3. Validálás Contextus szinten (Entity Framework)

A contextus osztályunk (NorthwindEntities) rendelkezik egy OnContextCreated parciális metódussal, melyben feliratkozhatunk a SavingChanges eseményre, ahol az összes függőben levő változtatáson végigmazsolázhatunk, és saját validációs logika alapján akár ValidationException-t is dobhatunk. Ahogy korábban is, a kivételt a DynamicValidator kapja el és jeleníti meg.

Remélem ízelítőnek elég lesz ennyi a validáció testreszabásáról az ASP.NET Dynamic Data-ban. A következő bejegyzésben a táblák megjelenítésének egyéni testreszabásáról lesz szó.

Kategóriák:Uncategorized