Zoltán's profileÁrvai Zoltán BlogjaBlogLists Tools Help

Blog


    July 20

    Silverlight 3.0 újdonságok – Navigation Framework

    Webes világban megszoktuk, hogy olyan klasszul lehet a linkeket passzolgatni egymás között, így mindig a megfelelő aloldal nyílik meg. Fontos ez a különböző WebCrawler-ek számára is. Azonban a Flash és a Silverlight jellegű alkalmazásoknál ez nem ilyen egyszerű. Az oldalon belüli navigáció nem volt köthető linkhez, adott link beírásával pedig nem jutottunk el egy silverlight aloldalhoz. Legalábbis irtó meló volt ilyen megoldásokat készíteni.

    Silverlight 3.0-ban megérkezett a Navigation Framework, aminek segítségével a fenti problémákat könnyen áthidalhatjuk.

    A Navigation Framework 2 fő objektumot tartalmaz:

    • Page – Egy adott oldal, amire navigálhatunk
    • Frame – A keret, amin belül a navigáció zajlik, ebben jelennek meg az egyes page-ek.

    Hozzunk létre egy NavigationDemo nevű Silverlight Application-t.

    A demo letölthető innen:

    Majd egy Pages Folder-be adjunk hozzá új Silverlight Page komponenseket. (HomePage, ProductsPage, ContactPage)

    clip_image001

    A MainPage-en alakítsuk ki a következő layout-ot:

    clip_image001[6]

    Megkaptuk a jobb felső sarokban a 3 menüpontot. A terület nagy érszét a frame fedi le, ebbe kerülnek majd az egyes Page-ek.
    Következő lépésben a HyperlinkButton-ok NavigateUri property-jét állítsuk be, hogy a megfelelő xaml oldalra navigáljanak el:

    clip_image001[8]

    Az egyes Page-ek belsejébe helyezzünk el egy mezei TextBlock-ot, az adott oldalra jellemző egyszerű felirattal, hogy lássuk valóban a navigáció sikerül a frame-ben.

    clip_image001[10]

     

    Na haladjunk szép sorjában:

    1. Jól látható, hogy a böngésző címsorában a Termékek szó szerepel. Ez annak köszönhető, hogy a ProductsPage.xaml-ben a Page Title Propertyjét a “Termékek” stringre állítottam.
    2. Az Url mezőben látható, hogy a Pages mappában található ProductsPage.xaml-ben vagyunk! A linket kimásolva, és egy másik böngészőben megnyitva, ugyanezt a látványt kapjuk, nem kell a HomePage-ről idenavigálnunk! Yay!
    3. Látszik a Frame contentje a megfelelő Page-re változott. Jelenesetben a ProductsPage-ben elhelyezett TextBlock látszik.
    4. A navigációs menünk pedig változatlan, hiszen a framen kívül helyezkedik el. A Products link pedig a kiválasztott!
    5. Az alábbi ábrán látható, hogy bizony a böngésző előre hátra gombjainak segítségével navigálhatunk a korábban megnyitott oldalak között, azaz van history támogatásunk is a silverlight alkalmazásunkon belül!!

    clip_image001[16]

    A link azonban elég csúnya, ennél kultúráltabb URL routingot is el lehet képzelni. Szerencsére a megoldás adott, az ún. UriMapper-ek segítségével. A csúnya URL-ünkhöz egy szép kultúrált elérési útvonalat mappelhetünk le. (meglátjuk később, hogy a szépségnél jóval nagyobb jelentősége is van a dolognak.)

    Irány hát az Application.Resources az App.xaml fájlban és készítsünk ilyen klassz kis mappingeket. Az UriMapper a System.Windows.Navigation namespace-ben található a System.Windows.Controls.Navigation assembly-ben.

    image

    Most, hogy kész a Mapping, visszamehetünk és a NavigateUri property-ket átírhatjuk a rövid verzióra:

    image

    Egy apróság maradt hátra, értesíteni kell a Frame-et, hogy használja az imént definiált UriMappert a feloldásra. Ezt a Frame UriMapper property-jének segítségével tehetjük meg. A korábban létrehozott “mapper” resource-ot hivatkozzuk meg:

    clip_image001[12]

    Az alkalmazást futtatva látjuk, hogy sokkal kultúráltabb a böngészőben megjelenő URL:

    clip_image001[14]

    Ennek az erőnek a birtokában nézzünk egy Master-Detail szituációt, amolyan “querystring-esen”. Készítsünk egy új Page-et ProductDetailsPage néven. A ProductsPage.xaml oldalon helyezzünk el egy listboxot és töltsünk bele Product-okat:

    clip_image001[18]

    A code-behind:

    image

    Na mit látunk? Loaded eseménykor betöltjük a Product-okat (lent a Product osztály definíciója).

    image

    A gombra klikkelve a listboxból kiolvassuk a kiválaszott termék id-ját. Majd a Page.NavigationService property-jének segítségével átnavigálunk a ProductDetails oldalra, mindezt úgy, hogy mögé egy “/” jel után az azonosítót odabiggyesztjük. Ez jelenleg nem sok értelemmel bír az SL alkalmazásunk számára. De most már sejtjük, hogy mi a teendő. UriMappinget kell készítenünk:

    clip_image001[22]

    Látjuk, hogy Querystring jellegű url-t mappelünk le a korábban meghivatkozott url-re. A “{}” jelek közé helyezett text, egyfajta helyettesítőként funkcionál. Jelenesetben a query string értéke az új url-ben a “Products/” után következhet.

    A navigáció megvan, mostmár csak a részletező oldalt kell elkészíteni, 3 adatkötött textblock jeleníti meg a Product adatait:

    image

    code-behind:

    image

    Az OnNavigatedTo függvény az adott oldara történő navigáció után fut le. A Page.NavigationContext property-n keresztül elérhető a QueryString! (ugye a mapping kitakarja) A mezőt kiolvasva kikeressük a megfelelő terméket, a többi pedig már csak sima adatkötés!

    Nézzük az eredményt:

    clip_image001[24]

    Böngészőből nyugodtam próbáljuk ki, hogy változtatgatjuk a linket:

    http://localhost:24764/NavigationDemoTestPage.aspx#ProductDetails/1
    http://localhost:24764/NavigationDemoTestPage.aspx#ProductDetails/2
    http://localhost:24764/NavigationDemoTestPage.aspx#ProductDetails/3

    Az adott ID-hoz tartozó termékek mind megjelennek. A Silverlight 3.0-ban megjelent Navigation Framework, így egy elég régi Silverlight problémát old meg. Talán már sejthető, hogy miként, de a következő bejegyzésben megnézzük, hogyan tudjuk mindezt alkalmazni SEO támogatásra.

    Ha van bármi kérdésetek, írjatok nyugodtan.

    Silverlight 3.0 újdonságok - Áttekintés

    Ez a kis cikk sorozat a Silverlight 3.0-ban megjelent újdonságokat igyekszik áttekinteni. Mielőtt az egyes témákba mélyebben belemennénk, nézzük miről is beszélünk. Az újdonságokat 4 csoportba sorolnám:

    1. Application Framework újdonságok
      • Navigation Framework, Search Engine Optimization
      • Adatkötés UI elemek között
      • Out of Browser képességek
      • Network Monitoring API
      • Local Messaging API
      • Új validációs támogatás
      • Assembly Caching
      • SaveFileDialog ablak
      • Szolgáltatás támogatás (Binary XML, RIA Services….)
      • Stílusok, CaretBrush és merged resource dictionary-k
    2. Tools Support
      • Blend 3.0 + SketchFlow Release Candidate
        • Blend API (Behaviors, Actions)
        • Adobe Photoshop és Illustrator Import
        • SampleDataSource
        • Intellisense és C# code editor
        • TFS Support
    3. Grafikai újdonságok
      • 3D-s támogatás
      • Pixel Effect-ek használata
      • GPU gyorsítás
      • Bitmap API
      • Szöveg kezelési újdonságok
      • Animációs újdonságok (easing)
      • Media kezelés újdonságok
        • TrueHD támogatás
        • RAW Audio és Video bitstream api
        • H.264 / AAC és MPEG-4 támogatás
        • Smooth Streaming (Smooth HD) – IIS Media Services

        És persze amellett sem mehetünk el, hogy a Silverlight 3-hoz megjelent a júliusi Silverlight Toolkit, amiben olyan nagyszerű vezélőket használatunk, mint:

        • DockPanel / WrapPanel
        • ViewBox
        • DataPager
        • DataForm
        • TreeView
        • AutoCompleteBox
        • ChildWindow
        • Accordion
        • Expander
        • Calendar

        A lista nem rövid. Remélhetőleg, mindenki talál magában benne igazi csemegét :)

        (A linkelés folyamatosan történik, ahogy az egyes postok elkészülnek!)

        July 17

        RIA Nuggets – DataPaging Issues

        Ma reggel RIA Services-t mutogattam és bátor oktató módjára demonstrálni akartam a késleltetett betöltést. (amit amúgy én nem nagyon használgattam még, mert voltak vele gondok korábbi ctp-kben) Valami ilyesmi kódom volt:

        clip_image001

        Aztán baromira meglepődtem, mert az előre betöltött 20 elemem pompásan megjelent, aztán a 3. oldalon 20-30ig és felette már semmi… Soha nem jött meg az adat. Néztem csak nagyokat. Végül elkönyveltem magamban, hogy bug :)

        Aztán hazajöttem és megpróbáltam reprodukálni… sikerült… Rá kellett döbbeni, hogy ez bizony nem megy. Aztán elkezdtem picit dühös lenni, hogy “nehogymánezmijezmá” :)

        Kicsit alaposabban beledebuggoltam, majd egy érdekes hibaüzenetet sikerült találnom:

        “The method 'Skip' is only supported for sorted input in LINQ to Entities. The method 'OrderBy' must be called before the method 'Skip'.”

        Ó igen… Entity Framework-öt használok, a DataPager meg lapozáshoz a Skip és Take-et használja, amiket csak rendezett listán lehet hívni… defaultból pedig ez nem az… szóval, így kellet módosítani a GetCustomers műveletet a DomainService-emben:

        image

        Mostmár pompásan működik!

        De ez miért nincs sehol rendesen ledokumentálva? Vagy csak az én figyelmem kerülte el?

        July 14

        Beárazták az Azure-t

        Megjött a várva várt hír. A Microsoft bejelentette, hogy pontosan mibe is fog kerülni alkalmazásaink és adataink hosztolása az MS felhőjében. Vessünk hát egy pillantást az árlistára:

        1. Windows Azure
          • Processzor idő: 12 cent / óra
          • Azure Storage: 15 cent / GB
          • Azure Storage felé tranzakciók: 1 cent / 10.000 tranakció (CRUD)
        2. SQL Azure (SDS)
          • Web Edition 1GB tárhellyel: 9.99 dollár
          • Busines Edition 10GB tárhellyel: 99.99 dollár
        3. .NET Services
          • Service Bus és Access Control tokenek, Üzenetek: 15 cent / 100.000 üzenet
        4. Extra költségek:
          • Hálózati erőforrások használata (sávszél:D):
            • Bejövö adatforgalom: 10 cent / GB
            • Kimenő adatforgalom: 15 cent / GB

        Emellé a Microsoft 99.95%-os rendelkezésre állást ígére, valamint 99.9%-os megbízhatóságot, ami a storage-et illeti.

        Ugyanakkor szeretném felhívni minden induló, vagy friss vállalkozás vezetőit, hogy a BizSpark program keretében hozzájuthatna ingyen Azure account-hoz, ami sok ezer órát tartalmaz!

        Ti, hogy látjátok? Hogy tetszik az árfekvés?

        July 13

        RIA Services 101 – Alapok

        Blogomat tekintve, talán nem titok senki számára, hogy kiemelt fontossággal bír számomra az adatkezelés és a szolgáltatások világa. A Silverlight és a WPF előretörésével, ahol a kliens lassan kezd a szín tiszta UI-ban kimerülni és inkább egy szolgáltatás orientált megközelítéssel mindent egy szolgáltatás fölé pakolni, megjelent a “hangya probléma”. Szorgos kis hangyák módjára kell ezredszer is az adatbázisban tárolt adatokhoz valamiféle CRUD-képes (Create/Read/Update/Delete) felületet készítenünk és szolgáltatásként kipublikálni. Persze minden adott, itt a WCF, itt az Entity Framework, vagy a Linq To Sql vagy bármi más. Ahogy közeledünk a .NET 4-hez egyre inkább elvárjuk az ilyen jellegű feladatok automatizálását, szolgáltatás rétegek legenerálását. Folyton harcolunk az olyan problémákkal, mint az authentikációs, authorizációs kérdések, a middle-tier és a client-tier közötti kódmegosztás (linking?) vagy a validációs, konkurrens műveletek, illetve a tranzakciók kezelése. A fentiek szellemében egy igazi gyöngyszemet szeretnék bemutatni.

        Sokat hangsúlyozzuk, hogy a Silverlight 3.0 végre egy igazi Line Of Business alkalmazásfejlesztési platform. Azonban ezt a címet csak a fenti problémák megoldására született .NET RIA Services-zel együtt érdemelheti ki. A cikk írásának pillanatában a RIA Services July Preview az aktuális és az első olyan verzió, amely nem rendelkezik már go live megkötésekkel.

        A .NET RIA Services célja, hogy leegyszerűsítse az n-rétegű alkalmazások fejlesztését, az ASP.NET (szerveroldal) és a Silverlight(kliensoldal) egymáshoz való közelítésével.

        image_6

        A rövid bevezető után készítsük el a RIA Services “Hello World”-jét.

        Egy új Silverlight Application projectet készítünk, és az első felugró ablakunk már tartalmaz némi érdekességet. Egy checkboxot.

        clip_image001

        “Enable .NET Ria Services”. Ez az ún. RIA Link. A Silverlight kliens és a Web alkalmazás között rögzíti a köteléket.

        Az ASP.NET site-hoz adjunk egy Entity data model-t. Én a northwind adatbázis Products tábláját fogom a designer felületre húzni. Majd új elemet veszünk fel, mégpedig egy Domain Service Class-t.

        clip_image001[6]

        A következő dialógus az iránt érdeklődik, hogy mit szeretnénk publikálni a Domain Service-ünk által, és hogyan.

        clip_image001[8]

        Az Enable Client Access checkbox-ot bepipálva, a szolgáltatáshoz megfelelő kliens kontextus kerül legenerálásra. A Products entitás halmazhoz szeretnénk read-only hozzáférést biztosítani.

        A generált osztály magáért beszél:

        clip_image001[10]

        A ProductService-ünk a LinqToEntitiesDomainService<> generikus osztályból származik, ami alapvetően a DomainService ősosztályból. Tetszőleges adatforráshoz készítheztő specifikus DomainService class. Kliensből hozzáférhető ([EnableClientAccessAttribute])
        Egy tagfüggvényünk van a GetProducts, amely visszatérési értéke IQueryable<>, amely jelzi, hogy a lekérdezések összefűzhetők lesznek, valamint a metódus törzsében egy linq to entities kifejezés áll. A függvény törzse tetszőleges módon kiegészíthető egyéb logikával, szűréssel, rendezéssel, stb…

        Most jön az izgalmas rész: F6 (Build)

        clip_image001[12]

         

        A kliens oldalon kódgenerálással előállt a ProductContext osztály (DomainContext leszármazott). Ez a class közvetlen hozzáférést biztosít a szerveroldalon definiált DomainService-hez.
        A ProductContext osztályban számos érdekességre bukkanunk:

        clip_image001[14]

        Az EntityList egy adatkötéshez gazdag funkcionalitással rendelkező speciális gyűjtemény. A GetProducts DomainService műveletünkhöz készült egy GetProductsQuery() függvény, amelynek segítségével komplex lekérdezéseket készíthetünk. Minden DomainContext-hez tartozik egy EntityContainer, amely a változás követés menedzsmentjéért felelős. Az ábrán látható, hogy a container, olyan entitás listát készít a Products számára, amely az entitásokhoz csak olvasható (perzisztálás szempontjából) hozzáférést biztosít. (EntityListOperations.None)

        A generált entitás osztályok a szokásos “kellemes” módon működnek:

        1. Számos bekapcsolódási pontot kínálva partial metódusok által
          • On[PropertyName]Changing(value)
          • On[PropertyName]Changed()
        2. És a megfelelő interfaceket impelementálva Entity ősosztályból származva:
          • INotifyPropertyChanged
          • IEditableObject
          • IChangeTracking
          • IRevertibleChangeTracking

        (ez utóbbiakat szívesen használja pl a DataForm)

        Ezek után írjuk meg az első lekérdezésünket.

        MainPage.xaml.cs (Code-Behind)

        clip_image001[16]

        MainPage.xaml

        image IE8 :)

        clip_image001[18]

        Látható, hogy RIA Services segítségével lekérdezések írása nem túlságosan bonyolult. Persze még nagyobb segítséget kapunk Silverlight esetén a DomainDataSource elnevezésű objektum által. A következő bejegyzésben a DomainDataSource-ot vesszük górcső alá.

        July 10

        SL 3.0 + Blend 3 Sketchflow RC + RIA Services July Preview

        És itt van az aranyeső :)

        Nézzük sorjában Silverlight 3

        Persze ez még csak fejlesztőknek szól. Nagyon tessék vigyázni, a blend 3 preview nem megy a végleges sl3-al!!! Tessék leszedni az RC-t! :)

        Expression Blend 3 + Sketchflow Release Candidate

        És igen, benne van a sketchflow is!

        Ria Services July Preview

        Ez sem maradhat ki, az okosok azt mondják, ez már go live ready, ugyanakkor az ado data serivces-zel való integráció használata még nem javasolt éles alkalmazásban!

        January 04

        Lusta és Mohó kiértékelés ADO.NET Data Services-ben

        Egy fórum post késztetett arra, hogy írjak erről a bizony egyáltalán nem elhanyagolható témáról. Valójában egy nem túl dokumentált technológiáról van szó, főleg ha a hozzátartozó Linq providerről van szó. Szóval a kérdés, hogy hogyan töltsük be egy entitáshoz tartozó gyermek entitáshalmazt (nyilván valami reláción keresztül), sőt mi van akkor ha a gyermek entitáshalmazhoz tartozó egyik gyermek entitáshalmazt is be szeretnénk tölteni, és ezek kombinációját mindenki el tudja képzelni. Továbbá az is jó kérdés, hogy egyáltalán mikor töltsük be ezeket az adatokat? Rögtön egyetlen lekérdezéssel mindent húzzunk le (mohó kiértékelés), vagy esetleg akkor töltsük be a kapcsolódó entitásokat amikor ténlyeg szükség van rájuk (lusta kiértékelés)?

        A bejegyzéshez tartozó demó adatmodell a Northwind adatbázis alapján készült és azon belül annak egy részhalmazára, egészen pontosan a Customers, Orders, OrderDetails, Shippers 4-essel foglalkozunk most.

        image

        A példa alkalmazás egy egyszerű WPF kliens lesz, amelynek segítségével egy DataGrid-ben (WPF Toolkit része) jelenítjük meg az adatokat. A Cél a következő: a Customereket jelenítsük meg alapból, kiválasztva egy Customer-t lássuk a hozzátartozó Order-eket illetve az egyes orderekhez kapcsolódó Order_Details-eket. (Azaz a vásárlók megrendeléseit és azok részleteit)

        Mivel ADO.NET Data Services-ben nincs join, így csak a modell alapján kialakított kapcsolatokon tudunk navigálni.
        Pl: theCustomer.Orders, vagy theCustomer.Orders[0].Order_Details és így tovább. Mivel egy Rest alapú adatszolgáltatásról van szó így ezt url alapú lekérdezésekkel érhetjük el a következő módon:

        ALFKI ID-val rendelkező felhasználó
        http://localhost:6978/NorthwindDataService.svc/Customers('ALFKI')

        A hozzá tartozó összes megrendelés
        http://localhost:6978/NorthwindDataService.svc/Customers('ALFKI')/Orders

        A hozzá tartozó egyik megrendelés. Kézen fekvő lenne, de ilyet sajnos nem lehet...
        http://localhost:6978/NorthwindDataService.svc/Customers('ALFKI')/Orders(10643)

        (A vásárló adott megrendeléséhez tartozó részletek) Innentől kezdve ez sem megy...
        http://localhost:6978/NorthwindDataService.svc/Customers('ALFKI')/Orders(10643)/Order_Details

        A feladat egy pontig az előző gondolkodás nyomán megodható: A várásló egy adott megrendlése
        http://localhost:6978/NorthwindDataService.svc/Customers('ALFKI')/Orders?$filter=OrderID eq 10643

        Azután a megrendléshez tartozó részletek
        http://localhost:6978/NorthwindDataService.svc/Orders(10643)
        http://localhost:6978/NorthwindDataService.svc/Orders(10643)/Order_Details

        Azonban az egész megoldható egyetlen lépésben is, ún. eager loading (vagy mohó kiértékelés) segítéségével
        http://localhost:6978/NorthwindDataService.svc/Customers('ALFKI')/Orders?$expand=Order_Details

        A fenti példában az adott vásárlónk Megrendeléseit még le tudjuk kérni, utánna kérjük explicit módon az expand operátor segítségével az egyes megrendelésekhez tartozó össze részletet is.

        image

        Valami ehhez hasonló eredmény kapunk, ahol jól látható, hogy az 10643-as megrendelésen belül az ahhoz tartozó tulajdonságokon kívül ott szerepel még a teljesn Order_Details entitás (és még kettő bezárva a lap alján). Szóval ilyen formában az egész eredmény halmaz lekérdezhetővé vált. Természetesen a szintaxis nagyon sokat megenged, játszatunk kedvünkre:

        Az összes megrendelőhöz tartozó összes megrendelés
        http://localhost:6978/NorthwindDataService.svc/Customers?$expand=Orders

        Az összes megrendlőhöz tartozó összes megrendenlés és azok részletei
        http://localhost:6978/NorthwindDataService.svc/Customers?$expand=Orders/Order_Details

        Az összes megrendlőhöz tartozó összes megrendenlés és azok részletei, valamint a megrendlésekhez tartozó szállítók
        http://localhost:6978/NorthwindDataService.svc/Customers?$expand=Orders/Order_Details,Orders/Shippers
        (ezen a ponton kezd izzadni az IE, szóval csak óvatosan :) )

        Nézzük ezek ismeretében hogyan írjuk meg kliensünket? Két változatot is követünk, az egyik a mohó kiértékelés, a másik a lusta kiértékelés

        1. Eager Loading (mohó kiértékelés)

        Én erőssen Linq To ADO.NET Data Services párti vagyok, szóval nézzük, hogy az előbbi url-ek hogyan állíthatók elő Linq segítségével. Szerencsére elég egyszerűen :)

        image

        Az fenti query-t futtatva és debuggolva azt kapjuk, hogy az egyes Customers-ekben nincs egyetlen megrendelés sem. (szép is lenne, ha csak úgy magától betöltené...)

        image

        A megoldás a query-nk átalakítása és az Expand() Extension method használata.

        image

        A második query-t futtatva meggyőződhetünk róla, hogy az adatok valóban letöltésre kerültek

        image

        Külön szeretném felhívni a figyelmet a kékkel bekeretezett részre, ahol látjuk, hogy mifél query megy a service-ünk felé. (remélem ismerős :P)

        Már csak valami egyszerű megjelenítő felület kell hozzá, és mivel WPF-ben vagyunk gyorsan 3 datagrid alkalmazással vizualizáljuk az eredményt.

        image

        Egy datagrid a vásárlóknak, annak rowdetailstempalte-jébe egy másik datagrid a megrendeléseknek, majd annak a rowdetails-ébe egy harmadik datagrid a megrendelések részleteinek. Nagyjából a következő eredményt sikerült elérnünk, ha futtatjuk, majd kiálasztunk egy megrendelőt, majd a hozzá tartozó egyik megrendelést.

        image

        A mohó kiértékelés hátránya, hogy nagy mennyiségű adatot tölt lesz egyszerre, ugyanakkor a letöltést követően az adatok és a részletek azonnal elérhetők lesznek.

        2. Lazy Loading (lusta kiértékelés)

        Node mi van akkor, ha a nagy mennyiségű adatok letöltése (rengeteg felesleges információ, amire nincs is szükségem) számomra nem elfogadható. Mi van akkor, ha az előző gridbe csak azokat az adatokat szeretném letölteni, amiket a felhasználó kinyit. Igaz ekkor azzal kell számolnom, hogy minden kinyitásnál várni kell az adatok letöltésére, de talán ez elfogadhatóbb, mint több 10 ezer rekordot letölteni....

        Ez az ún Lazy Loading vagy lusta kiértékelés segítségével lehet. Hallelúja Linq To ADO DS, ezt is tudod!

        Miután szereztük referenciát egy objektum példányra (amihez tartozó egyéb adatrekordokat szeretnénk megjeleníteni, pl theCustomer.Orders) a DataServiceContext-ünknek azonnal jelezni kell, hogy mit szeretnénk betölteni. (ez egy újabb HTTP GET lesz!)

        image

        A fenti példában minden egyes customer-hez külön(!!!) betöltésre kerül a customerhez tartozó megrendelés!

        Ha a fenti példát szeretnénk átírni lazy loading-ra, akkor a következőt tehetjük:

        image

        Fel kell iratkozni a külső datagrideken a LoadingRowDetails eseményre. Itt elkövetjük a késő betöltést, majd frissítjük a datagridet (mert az adatmodellen sajnos változás értesítésnek nyoma sincs :( )

        Silverlight alatt sem sokkal bonyolultabb a dolog, csak az aszinkron megvalósításra kell figyelni.

        Akit érdekel a pontos implementáció, a forráskódot letöltheti az alábbi linkről:

        DataService Demo Application

        December 02

        ADO.NET Data Services és a Silverlight 2.0

        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.

        image

        (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! 

        image

        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!

        image

        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.

        image 
        (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:

        image

        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>:

        image

        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.

        image

        Í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ő:

        image

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

        image

        ...az alábbi eredményt kapjuk:

        image

        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!

        image

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

        image

        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.

        image

        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)

        image

        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.

        image

        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ő...

        image

        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.

        image

        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.

        image

        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ó.

        December 01

        ASP.NET Dynamic Data Intro (2.)

        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.

        image 
        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:

        image 
        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

        image

        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.

        image

        2. RangeValidator

        image

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

        image

        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.

        image

        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:

        image

        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.

        image

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

        image

        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:

        image

        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!

        image

        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ó!

        image

        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ó.

        November 27

        ADO.NET Data Services Introduction

        Az ADO.NET DS célja és szerepe

        Fejlesztés során időnk jelentős részét az teszi ki, hogy adathozzáférési/adatszolgáltatási rétegeket gyártunk. Ha eljutottunk odáig, hogy sikerült kinyernünk az adatbázisból a szükséges adatokat, a következő feladatunk, hogy azt megfelelő formában átadjuk a kliens alkalmazásunknak, mondjuk HTTP-n keresztül. A másik irány se sokkal kellemesebb, a létrehozott entitás példányokat kell egy webszolgáltatás segítségével beszúrnunk az adatbázisunkba.

        Eddig két irányban gondolkodhattunk. Mezei webszolgáltatásokban (asmx), vagy WCF-es szolgáltatásokban. Képzeljük el a következő helyzetet. Kell csinálnunk egy GetProducts metódust, ami visszaadja az összes termékünket. Aztán rájövünk, hogy többféle szűrést is alklamazunk kell, így elkészülnek a GetProductByID, a GetProductsByCategory, GetProductByName stb. metódusok. Nyilvánvalóvá vált, hogy szükség van egy olyan rugalmas adatcentrikus szolgáltatásra, amely az összes szükséges, ilyen jellegű CRUD (create / read / update / delete) műveletet automatikusan előállítja.

        Ezt a mára már robot, vagy szolgamunkának nevezhető terhet, igyekszik az ADO.NET Data Services levenni a vállunkról.

         

        Mit nyújt az ADO.NET DS?

        Az ADO.NET Data Services az adatbázisunk fölé generál egy adatcentrikus szolgáltatás réteget, amely támogatást nyújt a leggyakoribb feladatokra, mint a CRUD műveletek, a lapozás, szűrés, rendezés, vagy az egyszerű, adatok közti navigáció. Az adatok kipublikálása ATOM és JSON segítségével( ez utóbbi különösen jó hír az AJAX fejlesztők számára), a hozzáférés pedig URL alapon történik, ahol az URL a szolgáltatás címének, illetve az entitások és a műveletek nevének összefűzéséből jön létre. Így bármilyen alkalmazás számára könnyen hozzáférhetővé válik.

        A szolgáltatás az adatbázisból az adatok kiolvasására, olyan ORM (object relational mapping) eszközöket hívhat segítségül, mint az ADO.NET Entity Framework, vagy a Linq To Sql. Meg kell jegyeznünk (bár ez már sejteti), hogy bármilyen CLR objektum tekinthető adatforrásnak és kipublikálható, ha az implementálja az IQueryable<T> interface-t.

         

        ADO.NET DS Szolgáltatás készítse

        Az ADO.NET DS-nek szüksége van az adatmodellre, ami fölé generálhat szolgáltatás réteget. Ezért az első lépés egy adatmodell elkészítése. Itt most a Northwind adatbázishoz készítünk egy adatmodellt. (lásd: Entitás modell elkészítése)

        Vegyünk fel egy új elemet a projekthez, ami egy ADO.DET Data Service legyen.

        image

        Megnyitva a NorthwindDataSerivce.cs-t, a következő látvány tárul elénk:

        image 
        DataService<T> ősosztályból származtatunk, ahol a T generikus paraméter az adatmodell típusa kell legyen. Jelen esetben NorthwindEntities. Cseréljük is ki a fenti sort, erre:

        image 

        Azonban még nem vagyunk kész! Ugyanis ha most elindítanánk az alkalmazásunkat egy üres service document bámulna ránk. Nem mutatna egyetlen entitást sem. Az entitásokhoz a hozzáférést egyenként engedélyezni kell. Ezt a szolgáltatás InitializeService metódusában tehetjük meg a config.SetEntityAccessRule hívással:

        image
        (Amennyiben a tábla név helyére *-ot írunk, az összes táblára érvényes lesz az adott EntitySetRight-s beállítása)

        Itt most a két táblára teljes jogosultsági kört engedélyeztünk. Természetesen ez is testreszabható az EntitySetRights enumerációval.
        Most futtatva az alkalmazásunk, máris láthatjuk a kipublikált két entitáshalmazt.

        image

        Az ADO.NET DS használatba vétele

        Fentebb már említsére került, hogy az ADO.NET DS szolgáltatásunkhoz való hozzáférésre URL alapú megközelítést alkalmazunk. Ez egy egyszerű címzési séma. A formátum a következő:

        http://host/<service>/<EntitySet>[(<Key>)[/<NavigationProperty>[(<Key>)/...]]]

        Az URL-t ennek megfelelően 3 részre bonthatjuk.

        1. A szolgáltatás elérési útvonala

        pl. http://localhost:6627/WebSite15/NorthwindDataService.svc/

        Ezen a címen, hogy XML fájlt, az ún. Service Document-et kapnánk, amely egy leírás arról, hogy az adott szolgáltatás milyen adatokat publikál ki. (lásd legutóbbi ábra)

        2. Az entitás neve

        pl. http://localhost:6627/WebSite15/NorthwindDataService.svc/Products

        A kapott eredmény egy AtomPub formátumú dokumentum, amelyben az összes termék adatai megtalálhatók. Az entitás neve mögé szűrható zárójelben az elsődleges kulcs, ami alapján egy adott terméket kiválaszthatunk. pl. http://localhost:6627/WebSite15/NorthwindDataService.svc/Products(11)

        3. Navigációs property

        pl. http://localhost:6627/WebSite15/NorthwindDataService.svc/Products(11)/Categories

        Ha a két entitás között van reláció, akkor azon a kapcsolaton könnyen mozoghatunk. A kapott eredmény a 11-es ProductID-val rendelkező termék kategóriájának részletei.

        Megjegyzés: Egyéb kulcsszavak is használhatók például, rendezésre, szűrésre, metaadatok kiolvasásra stb... Az ilyen kulcsszavakat az URL után egy ”?”-et követően sorolhatjuk fel $kulcsszó szintaktikával. Kulcszavak egymásután füzése a ”&” karakterrel lehetséges.

        Például:

        http://localhost:6627/WebSite15/NorthwindDataService.svc/Products?$orderby=ProductName

        Azaz rendezzük a termékeket nevük szerint ábécé sorrendben.

        http://localhost:6627/WebSite15/NorthwindDataService.svc/Products?$orderby=ProductName&$filter=UnitPrice gt 100

        Csak az 100-nál drágább termékeket jelenítjük meg abc sorrendben.

        Az ADO.NET Data Services lényegében minden .NET-es klienssel képes együtt működni, legyen az WPF, WinForms, Silverlight, AJAX, vagy egy sima Console-os alkalmazás. Egy lépéssel ismét közelebb jutottunk a gyors alkalmazás fejlesztés világához.

        Néhány példa, amit érdemes kipróbálni:

        NorthwindService.svc/Customers(’ALFKI’)
        NorthwindService.svc/Customers('ALFKI')/Address
        NorthwindService.svc/Customers('ALFKI')/Address/$value
        NorthwindService.svc/Customers?$orderby=City
        NorthwindService.svc/Customers?$orderby=City desc
        NorthwindService.svc/Customers?$filter=City eq ’London’
        NorthwindService.svc/Customers?$filter=City eq 'London'&$orderby=ContactName
        NorthwindService.svc/Customers('ALFKI')/Orders

        ADO.NET Data Serivces - Saját műveletek (ServiceOperation)

        Ha olyan műveletet szeretnénk végrehajtani, amit URL alapú lekérdezéssel nem tudunk, akkor készíthetünk saját műveleteket, ún. Service operation-öket. A visszatérési érték IQueryable<T> annak érdekében, hogy a lekérdezés tovább alakítható legyen.

        image

        Akárcsak az entitáshalmazokat, a ServiceOperation-t is engedélyezni kell:

        image

        Ezt követően a metódus hívható az alábbi módon:
        http://localhost:6627/WebSite15/NorthwindDataService.svc/GetProductsByCategory?categoryName='Beverages'
        Több paramétert "&"-el elválasztva lehet felsorolni.

        ADO.NET Data Services - Lekérdezések felügyelete(QueryInterceptor)

        Előfordulhat, hogy szeretnénk kontrollálni az entitásokhoz a hozzáférést, szűrni a visszaadott eredményhalmazt stb. Ezt QueryInterceptor-okkal tehetjük meg.

        image 

        A QueryInterceptor attribútumban jeleztük, hogy a Products entitáshalmazhoz tartozik az interceptor. Ez a metódus egy lambdakifejezést ad vissza, ami részt vesz a végső Query felépítésében. Azaz, ha egy feltételt ide (szűrést) ide elhelyezünk, az a végső eredményhalmazt befolyásolja, ezért ez a bonyolult visszatérési érték. (Productokon értelmezünk egy logikai (bool) kifejezést, amiből kifejezés fát építünk)

        Ha futtatjuk a szolgáltatás és a Products entitáshalmazt vizsgáljuk, láthatjuk, hogy csak az italok jelennek meg.

        ADO.NET Data Services - Módosítások felügyelete(ChangeInterceptor)

        Nem csak a legkérdezés, de az adatmódosítás is vezérelhető interceptorok segítségével.

        image

        A ChangeInterceptor attribútum paramétereként megadjuk, hogy melyik entitáshalmaz módosítását szeretnék figyelemmel követni. A metódus első paramétere referencia az entitásra(új elem, módosított elem, törölt elem), a második a művelet jellege. (add, delete, change)

        A folytatásban átmegyünk a kliensoldalra, és megnézzük, hogy Silverlight 2.0-ból, hogyan lehet egy ADO.NET DS szolgáltatást meghívni.

        ASP.NET Dynamic Data Intro (1.)

        Rohant már oda hozzád rendszergazda azzal, hogy extra gyorsan kéne egy webes felület az adatbázisához, ahol a tábláinak az adatait tudja felügyelt módon szerkesztgetni, feltölteni? Vagy netán neked fejlesztési időben jól jött volna egy ilyen felület, ahelyett, hogy a management studio-t használtad volna ilyen célra? Esetleg jól jött volna intranetes alkalmazásodhoz egy olyan out-of-the box site, ahol a tábláid adatai automatikusan vizualizálásra kerülnek, rögtön szerkeszthető formában? Ha igen, akkor a dynamic data a neked való technológia!

        Az ASP.NET Dynamic Data a .NET 3.5 SP1-ben került bemutatásra, és elsődleges célja, hogy egy adatmodell fölé épülve az entitásokat rugalmasan, testreszabható formában megjelenítse, és egyfajta CRUD-ot támogató felületet építsen fel dinamikusan. Ahelyett, hogy tovább elmélkednék a technológiáról, egy apró tutorial formájában inkább bemutatnám!

        Visual Studio 2008-ban, mikor egy új Dynamic Data Page-et szeretnénk készíteni az első döntés, amivel szemben találjuk magunkat, hogy Linq To Sql által készített adatréteget, vagy inkább az Entity Framework által generált adatréteget kívánjuk használni!

        image

        A Dynamic Data Entities Web Application mögött ADO.NET Entity Framework dolgozik, míg a sima Dynamic Data Web Application mögött Linq To Sql. (Itt most az előbbit választom)

        A következő lépés az adat modell elkészítése, ami alapján a Dynamic Data felépítheti dinamikusan az oldalakat. A projekthez egy új elemet kell felvennünk, még pedig egy ADO.NET entity data model-t!

        1. Az entitás modell elkészítése

        image

        A wizard-on kiválasztjuk, hogy adatbázisból generáljuk az entitás modellt, beállítjuk, hogy a Northwind adatbázishoz szeretnénk csatlakozni. A web.config-ba pedig elmentjük a hozzá tartozó connection stringet!

        image

        A következő lépésben kiválasztjuk az összes táblát, így az Entity Framework az összes táblázhoz generál entitás osztályokat. A modell névtere a NorthwindEntities lesz.

        image

        A studio legenerálja az entitás modelt, mentsük el, majd be is zárhatjuk!

        2. A Dynamic Data Site életrekeltése

        Miután a modell kész, valahogy el kell érnünk, hogy a Dynamic Data ezt a modellt használja fel adatforrásként. Ezt a Global.asax fájlban tehetjük meg. A fájlt megnyitva elénk tárol a statikus RegisterRoutes függvény. Ahogy az igen bő komment is mondja, szűntessük meg a kikommentezést az alábbi soron, adjuk meg az adatkontextus típusát, illetve a ScaffoldAllTables tulajdonságokat false-ről állítsuk true-ra. Ezzel két dolgot értünk el. Az egyik, hogy a dynamic data most már tudja, honnan gyűjtse be az adatokat, illetve, hogy default-ból, minden táblát vizualizálnia kell. (ScaffoldAllTables)

        image

        Ezen a ponton futtathatjuk is az alkalmazásunkat! A szemünk elé tárul egy default témával ellátott oldal, ahol láthatjuk a tábláink listáját.

        image

        Bármelyik tábla nevére klikkelve a hozzá tartozó adatok megjelennek. Íme a Products tábla:

        image

        Kicsit nyaggatva, a következő dolgokat vesszük rajta észre...

        1. A Lista szűrhető a fenti dropdownlist-ek alapján
        2. Az oszlop fejlécére klikkelve rendezésre kerül a lista
        3. Van edit, delete és details funkcionalitás
        4. Új elem beszúrására találunk link-et
        5. Szabályozható futásidőben, hogy hány elemet jelenítsünk meg egy oldalon
        6. A Categories szekcióban CategoryID helyett, a kategória neve jelenik meg!!!
        7. Minden postback aszinkron, azaz az oldalunk AJAX-et használ.
        8. A Products táblából az asszociációkon keresztül könnyedén átnavigálhatunk egy másik táblára, pontosabban a másik táblában található, az adott sorhoz kapcsolódó adatokhoz.

        Máris kaptunk egy csomó out-of-the-box funkcionalitást, pedig kódot még gyakorlatilag nem is írtunk.

        Az edit, illeve az insert linkre kattintva sem kell csalatkozunk. A Editnél a barátságtalan SupplierID helyett, egy dropdownlistből választhatjuk ki a Supplier nevét. Továbbá, mind update és mind insert esetén kapunk validátor vezérlőket, amik ellenőrzik az adatok helyességét. Például ha valami az adatbázis szinten "not null", nem maradhat kitöltetlenül, ahhoz kapunk RequiredFieldValidator-t!

        image

        4. Mi történik?

        Nem egyszerű kódgenerálásról van szó. Nagyon nem. A Solution Explorerben dalálható Dynamic Data mappára vessünk egy pillantást.

        image

        Nyoma sincs Products, vagy bármilyen más adattábla specifikus oldalnak. Valójában PageTemplate-ek vannak.

        1. List.aspx - megjelenítés
        2. Edit.aspx - szerkesztés
        3. Insert.aspx - beszúrás
        4. Details.aspx - adott sor részleteinek megjelenítése
        5. ListDetails.aspx - Inline szerkesztés

        Ha megnyitjuk az egyik ilyen aspx-et látjuk, hogy semmilyen tábla specifikus adat nincs benne, teljesen általános sablon dokumentumok. Sőt arra sem nagyon találunk semmi vonatkozást, hogy az egyes oszlopelemek hogyan jelennek meg.
        Erre szolgálnak segítségül a FieldTemplate-ek. Itt az egyes típusokhoz tartozik egy-egy UserControl. Mind a megjelenítéséhez, mind az edit állapotához. Például a Boolean.ascx-ben egy egyszerű checkbox van. Azaz az egyes típusoknak egy UserControl felel meg, amit mi testreszabhatunk itt, vagy kicserélhetünk teljesen, netán új típusokat vezethetünk be.

        Ha futtatjuk az alkalmazást és egy pillantást vetünk az URL-re, igazán érdekes dolgot tapasztalhatunk. A Products táblára klikkelve az alábbi címet láthatjuk:

        image

        Garantáltan nincs Products mappám, és a List.aspx sem tartalmaz semmi product specifikusat, az adat mégis helyesen jelenik meg.
        Az egészért az ASP.NET Routing felelős. A Global.asax-be visszavándorolva a következő kódrészleteket találhatjuk még:

        image

        A fenti kódrészlet egy DynamicDataRoute-ot definiál, {table}/{action}.aspx formában, ahol az action lehet list, details, edit, és insert. Ez a kódrészlet határozza meg, hogy a Products/List.aspx értelmes URL legyen, és a products táblát tekintse aktuális entitásnak, amin a List.aspx sablon kerül alkalmazásra. Az engine elemzi a tábla adatait és a List.aspx-ben található sablon alapján megjeleníti az egyes adatokat, a típusuknak megfelelő usercontrolok alapján.

        Láthatjuk, hogy nem mezei kódgenerálás történik, hanem egy jóval dinamikusabb oldalösszeállítás.

        3. Testreszabás, avagy mit lássunk és hogyan?

        Szép, szép, de hogyan lehet testre szabni? Hogyan zárhatok ki táblákat, oszlopokat a Scaffolding alól, ha nincs egy string, amit felül tudok definiálni, akkor hogy tudom megváltoztatni a nevét egy adott oszlopnak, vagy akár az egész táblának? Az entitás modellt kell módosítanom?

        A modell kiegészíthető metadatokkal, amelyek segítségével a dynamic data a megjelenítést módosíthatja. Az entitás osztályaink partial-ként vannak megjelölve, így egy külső class fájlban nyugodtan írhatok hozzá ezt-azt. A metadatok attribútumok formájában kapcsolódhatnak az entitásokhoz és a tulajdonságokhoz. Ezek az attribútumok a System.ComponentModel.DataAnnotations névtérben találhatók meg. (külön dll-ben szerepel, más technológiák is adaptálhatják)

        Nézzünk néhány egyszerű példát:

        1. A Territories tábla kizárása a megjelenítésből. (Add new class: Territories.cs)
          Az entitás osztály fölé helyezve a ScaffoldTableAttribute-ot, false értéket átadva neki, kizárhatjuk a táblát a vizualizációból.

          image 
        2. Oszlop kizárása - Products táblából a ProductID kizárása
          Ha tagokra kívánunk hivatkozni akkor szükségünk lesz egy osztályra, ami az entitások tulajdonságaival kapcsolatos metaadatokat írja le. Ezt követően az entitás osztályon jelezni kell, hogy melyik osztály tárolja a metaadatokat. Ezért definiálunk egy belső metadataosztályt, ahol az egyes property-kre hivatkozhatunk. A property típusa lehet object, ez a dynamic data-t nem érdekli. Mivel a ProductID-t zárnánk ki, ezért a ScaffoldColumn attribútumot állítjuk false-ra a ProductID property-n.

          image
        3. Oszlop átnevezése - UnitPrice oszlop átnevezése Price mezőre
          A metadata osztályban a UnitPrice mező elé a DisplayName attribútumot vesszük fel, ahol paraméterként megadjuk, hogy mostantól az mező neve mindenhol Price legyen.

          image
        4. Tábla átnevezése - Products tábla átnevezése Termékek táblára.
          A változás entitás színtű, így a Products Entitás osztályunkat értinti a DisplayName attribútum.

          image
        5. Értékek formázása - UnitPrice oszlopban található értékek megjelölése pénznemként
          A ProductMetadat osztályban a UnitPrice property fölé elhelyezzük a DisplayFormat attribútumot, a DataFormatString paramétere pedig legyen a {0:C} formázó string.

          image
        6. Dátum formázása
          A dátumok még az óra, másodperc információt is tárolják. Az Orders táblára klikkelve látjuk, hogy több oszlopban is szerepel ez a probléma. A megoldás nem az, hogy egyenként változatjuk meg a propertyk DisplayFormat-ját, hanem a típushoz (DateTime) tartozó usercontrol-on változtatunk. Ha megnyitjuk a DateTime.ascx-et a következőt látjuk. A FieldValueString tárolja a dátumot string formátumban.
          image 
          Ezt kell lecserélnünk az alábbira.

          image

          Így az alkalmazásunkban az összes dátum formázásra került, hiszen mind ezt a usercontrolt fogja használni.

        Na mára ennyi elég... :) Folyt. köv.
        A következő cikkben validáció testreszabásáról, illetve Custom Page-ekről (oldalak egyéni testreszabásáról) lesz szó.

        October 14

        Elérhető a Silverlight 2 végleges változata

        Végre! Megkaptuuuk! :) Nagy újdonságokra senki ne számítson, hiszen azokat megkaptuk az RC0-ban... De ez most már a végleges.. van plugin meg minden, meg egyre jobb WFP kompatibiltást... állítólag :) Ahogy várható volt a blend SP1 (nem preview) lett a SL2 IDE-nk :)
        Akit érdekelnek a részletek, olvashat róla Tim Heuer blogjában: http://timheuer.com/blog/archive/2008/10/14/silverlight-2-released-officially.aspx
        Egyébként meg irány a silverlight.net!
        September 26

        Silverlight 2.0 RC0 Publikus Release

        Ma reggel szép napra ébredtem! Aztán picit besötétült :)

        Na a jó hír, hogy kaptunk Silverlight 2.0-ból egy Release Candidate-et. És akkor a rossz hír: Only Developer Version :(

        Szóval plugin az nincs, ami viszont van az az Expression Blend 2 SP1 RC Preview, VS2008-hoz a szokásos Tools, amiben benne van az SDK, a runtime, a template, meg minden, ami szem szájnak ingere :) Na szóval továbbra is ott tartunk, hogy ez csak egy developer version, plugin nincs, ha valamit ezen a verzión fejlesztünk és kirakjuk a netre, akkor a felhasználó szája csak görbülni fog, mert plugin csak a beta 2-höz van továbbra is.

        Mielőtt sikongatva kirohannánk a szobából, hogy jajj de jó, akkor ez engem nem is érdekel, gondoljuk csak végig hogy mit is jelent ez... Egészen konkrétan azt, hogy a végleges silverlight release nem csak kicsit, hanem nagyon nem lesz kompatibilis a beta 2-es silverlight alkalmazásokkal (ez mondjuk várható volt). Ez az RC0 meg arra szolgál, hogy felkészüljünk rá. A cél, hogy egy tesztkörnyezetben elkészítsük a beta 2-es alkalmazásunk RC0-ás (amely, remélhetőleg ténylegesen kompatibilis kódot eredményez a végleges release-zel) változatát, hogy amint publikus a Silverlight 2 RTM, azonnal harcba küldhessük az RC0-ra upgradelt(??) beta 2-es alkalmazásunkat.

        Na szóval örömre nincs ok, megint lehet bújni a breaking changes listát, ami elég vaskosra sikerült. Van benne egy csomó változatás, amiről foggalmam sincs mi, vagy egy csomó, ami komoly korlátozás, van egy csomó, ami ésszerű változtatás, és van egy csomó hiányosság, amire nem látok a listában semmi hivatkozást. (Pl hogy a DependencyObject-ből nincs származatatás beta 2-ben és reméltem, az RTM-ben majd lesz...)

        Na szóval vegyes érzelmek kicsikét... Remélem használat közben majd átlátom a háttérben megbújt zsenialitást :)

        Na de hogy jókat is mondjak,  a vezérlők:

        Van ProgressBar!!!, vagy ComboBox!!!, van PasswordBox!!!, van MessageBox!!! meg új Skin templatek a vezérlőkhöz! Egyébként a vezérlők jó része, amiről már sokat hallhattunk (mert igen, készül még egy csomó, például TreeView, Expander, DockPanel, WrapPanel, AutoComplete, Accordion stb... ) nem lesznek benne a core-ban

        Viccet félretéve tessék megnézni a listát és tessék komolyan venni, nem véletlen ez az RC0, képet kaphatunk arról, hogy milyen lesz majd a final release. Én is jól nekiállok és összetúrom, hogy lássam mi a pálya :)

        July 03

        A Dictionary nemlétező sorosíthatósága

        Érdekes probléma vetődött fel a prog.hu társalgójában. Dictionary-t kellene xml sorosítani. Valljuk be az igény jogos. Szokásunkhoz híven püföljük is a billentyűzetet bőszen, aztán mikor futtatjuk azt kapjuk, hogy IDictonary-t nem lehet sorosítani. Lázasan turkálva az MSDN-t arcunkba csap a hideg levegő: "Nem volt idő implementálni". Két féle ember van. Az egyik itt leül és szidalmaz mindent és mindenkit akit ér. A másik, aki ezen a problémán könnyen átlibben (mert még emlékszik arra, hogy lehet programozni is a kattingatáson túl) és neki ár írni egy wrappert, vagy saját gyűjteményt. Nos én ez utóbbi csoportba tartozom.

        Először is tudni kell, hogy egy generikus Dictionary-ben KeyValuePair-ek vannak. Na most mivel az XML sorosító egy rakás követelményt támaszt az osztállyal szemben, aminek a KeyValuePair (úgy fest) nem tud megfelelni, ezért kell egy saját ilyen Entry osztály. Előkapjuk generikus típus ismereteinket és vmi hasonlót pötyöghetünk le.

        image

        Ugye miről van szó, két változóm van, egy Kulcs (T típusú) és egy Érték (U típusú). Kell bele egy default konstruktor, a mezők publikusak kell, hogy legyenek, akár csak az osztály, és két attribútumot is elhelyzeztem a változók elé, jelezvén, hogy az xml kimenetben attribútumként jelenjenek meg. A KeyValuePair-t kiváltottuk.

        Következő körben kéne maga az a saját gyüjtemény, vagy valami hasonló, ami eltárolja nekem ezeket az entry-ket. Most egy sima generikus belső listát hoztam e célból létre.

        image

        Úgy elsőre valami ilyesmit skiccelnék fel. Nyilván van ennél szebb megoldás is, de nekem ez most kellően egyszerű :)

        A kényelmesség kedvéért csináltam egy Add függvényt is, indexer most lemaradt. Minden add hívás, egy ilyen Entry példányt hoz létre és elhelyezi a belső listába, amihez egyébként az Items propertyn keresztül hozzá lehet férni. Egy-két attribútumot is elhelyeztem annak érdekében, hogy az XML kimenet egyszerű és rövid és áttekinthető legyen. Innetől kezdve egészen nyugodtan használhatjuk az új Dictionary-nket. Pl így:

        image

        Ebben a példában egy olyan MyDictonary példányt hozok létre, ahol a kulcs és az érték is string, utánna simán xml sorosítom. Nyilván lehet saját gyűjteményeket is írni, Interface-ekből építkezni, vagy teljesítményre helyezni a hangsúlyt, ha valami komolyabbat szeretnénk.

        Az Xml formázásnak köszönhető a végeredmény a következő:

        image

        January 17

        Bevezetés a C# 3.0 újdonságaiba(4.) - Bővítő függvények

        A Bevezetés a C# 3.0 újdonságaiba sorozat utolsó cikkében a bővítő függvényekről (extension methods) lesz szó. Ez volt számomra az az újdonság, melytől egy picit fáztam eleinte. Én megrögzött híve vagyok az OO világnak, és hogy őszinte legyek számomra a bővítő függvények kilógnak az "encapsulation" (egységbe zárás) szellemiségéből. Vagy mégsem?

        Tegyük fel, hogy van egy A osztályunk. Szeretnénk kibővíteni a képességeit azáltal, hogy megírunk egy-két plussz függvényt hozzá. Ha nem tudunk hozzáférni, az A osztály kódjához, mert mondjuk csak egy DLL-ben áll rendelkezésünkre, akkor a OO paradigma szerint a természetes lépés, hogy származtatunk belőle egy B osztályt, és odapakoljuk az új függvényeinket. Nade mi történik akkor. ha az osztályt a készítője ellátta a sealed kulcsszóval? Akkor bizony nincs származtatás! Nem hagytak nekünk más választást, mint szögre akasztani OO elveinket és kétségbe esésünkben definiálni egy statikus osztályt, amiben elhelyezzük a statikus függvényeinket, melyek paraméterként kaphatják az A osztály egy példányát. Hát érezzük, hogy ez minden csak nem az igazi. Nem kapcsolódik a függvényünk az osztályhoz, senki más nem fog tudni a függvényeinkről (nincs intellisense), stb... Pedig nem járunk messze a megoldástól, első pillantásra a bővítő függvények is valami ilyesmit csinálnak.

        Bővítő függvény alapok

        A bővítő függvények segítségével kiterjeszthetjük egy osztály képességeit, származtatás nélkül. Bővítő függvények készítésekor az első paraméter-t megelőzi a this kulcsszó, ezzel jelezvén, hogy az első paraméterként megjelölt típus nem más, mint az a típus, amit kiterjeszt! Nézzünk egy egyszerű példát erre.

        image

        Ez a bővítő függvény az object osztályt terjeszti ki. Meghíváskor egyetlen paramétert vár(ConsoleColor), majd ezzel a színnel kiírja az adott elem ToString() által visszaadott eredményét a konzolra. Szándékosan mondtam, hogy egy paramétert vár, ugyanis az első paraméter az az a típus, amit kiterjeszt, jelen esetben a object osztály.

        Nézzünk egy valamivel hasznosabb példát. Szeretnénk minden gyűjteményünkhöz egy konzolra kiirató metódust.

        image

        Azokat a típusokat tudjuk felsorolni, melyek implementálják az IEnumerable interface-t, így kézenfekvő, hogy bővítsük ki azt. This kulcsszó után jön a típus, majd a változónév. Ez a függvény, minden olyan objektumra meghívható, amely az IEnumerable-t megvalósítja, bejárja a gyűjteményt (list) és kiírja az elemeket a konzolra.  Elég egyszerű nem igaz?

        Felmerül a kérdés, hogy ez miben más, mint az eredeti elképzelésünk? Vessünk egy pillantást az alábbi kódrészletre, ahol meghívjuk a fenti bővítő metódust!

        image

        Úgy hívtam meg, mintha egy tag függvény lenne. Sőt, az intellisense fel is ajánlotta nekem! A WriteToConsole()-t meghívhattam volna egy int tömbre, egy arraylistre, egy string listára, stb...  Így már nem is haragszunk érte anniyra, nem igaz?

        Néhány fontos tényezőt szem előtt kell tartani, bővítő metódusok készítésekor:

        1. A bővítő metódusokat publikusnak és statikusnak kell deklarálni
        2. Csak statikus osztályban helyezhetünk ell bővítő metódusokat
        3. A bővítő metódusok csak a publikus adattagokhoz férnek hozzá

        A compiler fordításkor átnézi az összes statikus osztályt bővítő függvények után kutatatva, és fordítás időben elvégzi a szükséges kiterjesztéseket. Érték és referencia típusokat is egyaránt bővíthetünk. Az ajánlás szerint érdemes egy külön namespace-ben egy statikus osztályba pakolni az ilyen bővítő függvényeket, és inkább usingolni a namespace-t a projektünkben.

        Generikus bővítő függvények

        Érdemes egy kicsit külön tárgyalni a generikus bővítő függvényeket. A fenti példákban mindenhol meghatároztuk a típust, amit kiterjesztünk, így azt, hogy minél több típusra tudjuk alkalmazni, azzal váltottuk ki, hogy egy egyáltalános típust terjesztettünk ki, amiből származnak az egyéb specifikusabb típusok. (pl Objectből, IEnumerable-t valósítanak meg, stb...)

        Ennél azért lehetünk elegánsabbak és jóval gyorsabbak is, ha generikus szerkezetekben gondolkodunk. Például a fenti WriteToConsole függvényt átalakíthatjuk generikussá is.

        image

        Így a generikus IEnumerable<T> Interface-t bővítettük ki. Látható az alábbi ábrán, hogy az intellisense el is árul nekünk minden fontos információt a WriteToConsole függvényről.

          image

        Amit észre kell venni, hogy ezáltal nem egy típust bővítettünk ki, hanem azoknak egy halmazát! Ugyanis a bővítő metódusokat el kell készíteni IEnumerable<int>, IEnumerable<string>, IEnumerable<Books>, stb.. esetekre is. Ha az első WriteToConsoleWithColor függvényünket generikussá alakítanánk, és this object element helyett, this T element-et írnánk, akkor már is nem az object osztályt terjesztnénk ki, hanem egy tetszőleges típust (pl int, books, decimal, vagy akár object). (Az intellisense az írná nekünk, hogy (extension) void T.WriteToConsole<T>()

        Tagfüggvények Vs Bővítő függvények

        Jogosan merül fel a névütközés kérdése. Mi van akkor, ha van az A osztályunkban egy X függvény, és készítünk a B statikus osztályba egy X bővítő függvényt az A típushoz. Ha meghívjuk az A.X()-et, akkor mi fog lefutni az A osztály X tagfüggvénye, vagy a B osztály X bővítő függvénye? A válasz egyszerű, a tagfüggvény élvez elsőbbséget.

        A System.Linq névtérben rengeteg bővítő függvényt készítettek el nekünk, hiszen a Where, Select, Sum, Count, stb... egytől egyig ilyen bővítő függvények.

        A bővítő függvények használata biztonságos és kényelmes is. Fordítás idejű ellenőrzést kapunk használatukhoz, segítségükkel igen rugalmas megoldásokat készíthetünk. Azonban a kétkedés jogát fenntartom és azt tanácsolnám mindenkinek, hogy azért, mert ez a lehetőség megadatott, ne essünk neki mindennek és kezdjünk gőzerővel ilyen bővítő függvényeket gyártani. Akár hogy is, a bővítő függvények nagyszerű, ugyanakkor kicsit veszélyes eszközt is adtak a kezünkbe. Igyekezzünk továbbra is csínján bánni a használatukkal, próbáljuk megőrizni a kódunkban az átláthatóságot, a struktúráltságot és az egységbe zárást is próbáljuk szem előtt tartni, ugyanis a bővítő függvények kicsit talán kilógnak ebből az erős OO szemléletből.

        Remélem sokan hasznosnak találtátok ezt a bevezető jellegű cikksorozatot.

        January 10

        Bevezetés a C# 3.0 újdonságaiba(3.) - Lambda kifejezések

        Az egyik, talán első ránézésre legijesztőbb újdonság azok számára, akiknek a funkcionális programozás idegen, a C# 3.0-ban bevezetett lambda kifejezések. Segítségükkel kódot tudunk paraméterként átadni. Sokaknak biztos fel is sejlik a delegate-ek fogalma. Alapvetően egy sokkal kényelmesebb és érthetőbb szintaxis-t bocsátottak rendelkezésünkre, aminek segítségével névtelen függvényeket készíthetünk. Nézzük meg ugyanarra a feladatra, mindkét variációt:

        image

        Az első esetben a Where függvény paramétereként egy delegate-et(lényegében függvénypointert) adunk át és azt a kódot, amire mutat, rögtön definiáljuk is. Ugye ez a C# 2.0-ban bevezetett névtelen metódus. Ugye az történik, hogy minden listaelemre (adatbázisosan gondolkodva, "minden sorra") meghívjuk ezt a kódrészletet, ami akkor tér vissza igazzal, ha a szerző " John Steinbeck".

        A második esetben lambda kifejezést használtunk. Azt mondtuk, hogy csak azok a "b"-k (Book példányok) érdekelnek, melyekre igaz az, hogy az "Author"-juk "John Steinbeck". Tehát a szintaxis lényegében a következő: paraméterek => kifejezés

        Ami feltűnhet, hogy explicite nem adtuk meg "b" típusát. Ezt a fordító találta ki a Where bővítő metódus (későbbiekben részletesen) alapján. Természetesen explicite is meg lehet határozni a típust:

        image

        A fenti lambda kifejezés ún. predikátum, nem más, mint egy logikai kifejezés. Egy adott típusú paraméterre a visszatérési értek bool.

        A projekció olyan lambda kifejezés, mely egy adott típusú paramétertől eltérő típust ad vissza, és nem logikai kifejezés.

        image

        A fenti példa összegzi a könyvek árait. A Sum függvénynek lambda kifejezés segítségével meghatároztuk, hogy a b példány Price változója alapján végezze el az szummázást.

        A szokásos linq-s, sql-hez hasonló szintaxis, ilyen függvény hívásokba, a feltételek pedig lambda kifejezésekbe fordulnak. Sajnos ezt is nagyon jól kell ismernünk, ugyanis az szokásos sql-szerű szintaxisnak vannak korlátai, nem minden lehetőség érhető el benne.

        Az alábbi kódrészlet ugyanazt a lekérdezést valósítja meg, az egyik sql-szerű szintaxissal (query expression), a másik lambda kifejezésekkel és bővítő függvényekkel (extension methods).

        image

        A lambda kifejezéseket váltózóknak értékül adhatjuk felhasználva a Func delegate típust.

        image

        Előre deklaráltak nekünk jó pár ilyen Func delegate típust, hogy lambda kifejezéseink kellően rugalmasak lehessenek.

        A lambda kifejezések delegate-ként történő lefordítása és működése nagyon kényelmes, és kézenfekvő, ha memóriában lévő adathalmazokat szeretnénk lekérdezni (pl listák). De képzeljük el az a szituációt, amikor LINQ to SQL használatakor nagy adatbázisokból, nagy táblákból szeretnénk lekérdezni.

        Fejeltsük most el a Book osztályunkat, és képzeljük el, hogy van egy nagy adatbázisunk, és az egyik táblája a "Books" nevet viseli. Gondoljunk bele, hogy mi lenne, ha előbb betöltené memóriába az adatokat, és aztán kezdené a lekérdezéseket végrehajtani. Amit várunk az az, hogy a linq to sql a lambda kifejezéseinket fordítsa SQL lekérdezésekbe, majd azt küldje át az adatbázisnak, így csak az eredmény jusson el hozzánk. Ezt úgy érhetjük el, hogy ha Func típusú delegate-ek helyett kifejezés fákat (expression tree) alkalmazunk.

        image

        Látható, hogy nem sima Func-ként, hanem Expression<Func...>-ként deklaráltam a változót. Így nem készül IL kód belőle fordítás időben, csak futásidőben, így ki lehet értékelni, ahogy csak szükséges. Természetesen, ezt a LINQ to SQL elvégzi helyettünk. Az alábbi ábrán látható, hogy a Where bővítő metódus itt most már Expression<Func<Product,bool>> típusú predikátumot vár.

        image

        Fontos megjegyezni, hogy az Expression-ként deklarált kifejezést nem lehet közvetlenül futtatni, ellentétben a Func típusú delegate-ként deklarálttal.

        Nem szabad megijedni a lambdakifejezések használatától. Valójában egy nagyon jól használható, egyszerű dologról van szó, amit csak meg kell ismerni, rászánni egy órát és magunkévá tenni. Természetesen el lehet bonyolítani a végtelenségig, így akárcsak a többi újdonságot, ezt is használjuk óvatosan, hogy a kényelem ne menjen az olvashatóság, érthetőség kárára.

        December 31

        Bevezetés a C# 3.0 újdonságaiba (2.) - Object initializer-ek és anoním típusok

        A cél továbbra is a LINQ megismerése és elsajátítása, azonban ahhoz, hogy tudjuk használni ezt a kiváló technológiát, értenünk kell, hogy milyen egyéb nyelvi elemekre támaszkodik, hogyan működik. A linq működésének megértése elengedhetetlen követelmény, annak hatékony használatához.

        Object Initializers

        Ha C# 3.0-át megelőzően szerettünk volna egy osztályból egy példányt létrehozni, megfelelő paraméterekkel, akkor szükségünk volt egy megfelelően felparaméterezett konstruktorra. Ha a konstruktor nem volt képes minden számunkra szükséges tagváltozót beállítani, akkor kénytelenek voltunk property-k segítségével megtenni azt. Mostantól az object initializer-ek segítségével sokkal rugalmasabban tehetjük meg ugyanezt. Nézzük az alábbi osztálydefiníciót:

        image

        A fenti osztály konstruktora a PublishYear mezőt nem állítja be. Így ha azt be szerettük volna állítani, akkor azt egy külön hívással kellett eddig megtennünk.

        image

        Nézzünk néhány példát object initializer-t használva:

        image

        Az első példában az összes tagváltozónak értéket adtunk.  Tulajdonképpen a háttérben az object initializer implicit módon meghívta a default konstruktort, ezt követően beállította a mezők értékeit. Látható, hogy a szintaxis magáért beszél. A példányosítás során kapcsos zárójelek között kell megadni a mezőneveket, az értékadásokat, vesszővel felsorolva.

        A második példában látható, hogy csak két tagváltozót állítunk be, az "Author" mező üresen marad.

        A harmadik példában expliciten meghívjuk a nem default konstruktort. Pirospontért el lehet rajta gondolkodni, hogy mi történik akkor, ha a harmadik esetben nem csak a "PublishYear"-t, hanem a másik két tagot is beállítjuk, természetesen másik értékre, mint amit a konstruktornak adunk át.

        Az object initializerek segítségével csak publikus tagváltozók és property-k állíthatók be. A háttérben egy temporary változóba inicializál egy példányt, majd ha minden tagváltozót beállított, akkor a referenciát átadja a tényleges változónak. Ez a technika nagymértékben növeli a kód olvashatóságát és komoly rugalmasságot csempész bele. Természetesen az object initializer-ek egymásba ágyazhatók.

        image

        A fent látható osztálydefiníciókban kicsit furcsának tűnhetnek a property-k. Ezek az ún. auto-property-k. Nincs szükség nekünk privát változót létrehozni, majd hozzá a szokásos property-ket definiálni. A "prop Tab-Tab-ot" követően létrejövő sor mögött ott a teljes arzenál, ami a működéshez kell. A privát tag (amit nem látunk), a property get és set definíciója (amit szintén nem látunk) is automatikusan elkészül. Az alábbi kódrészlet egy rövidke példa a beágyazott object initializer-ek használatára:

        image

        A második tag egy osztálypéldány lesz, aminek tagváltozóit szintén object initializer segítségével állítottuk be. Ez eddig csupán egyfajta szintaktikai édesítőszer. Ahhoz, hogy meglássuk az igazi erősségét ennek az újdonságnak, meg kell ismernünk először az anoním típusok világát.

        Anonymous Types

        Létrehozhatunk ún. Anoním típusokat, melyek komoly szolgálatot tehetnek nekünk, például linq lekérdezések használatatkor.

        Íme egy egyszerű példa anoním típus használatára:

        image

        Látható, hogy a new kulcsszó után nem adtunk meg típusnevet, valamint object initializer segítségével adtuk meg és állítottuk be a tagváltozókat. A fenti definíció ekvivalens az alábbi osztálydefinícióval.

        image

        A var-ként deklarált anonymousType változó felveszi ezt az anoním típust. A compiler a következő típust készíti el nekünk. Igazi gyönyörűség! {Name = "<>f__AnonymousType0`2" FullName = "<>f__AnonymousType0`2[[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"} Ebből az érdemi információ számunkra a típus neve: "<>f__AnonymousType0`2" , valamint a típus két tagváltozója, ami egy string, illetve egy int. Azaz nem kellett egy fölösleges segédosztályt definiálnunk, a compiler elkészítette nekünk, nem volt szükség fölösleges többlet munkára. Láthatjuk, hogy a var micsoda nagy segítséget nyújt számunkra. Nélküle ugyanis lehetetlen lenne az anoním típus használata. Hiszen milyen típusú változónak tudnánk értékül adni? (object már kasztolás) A var-ként definiált változó implicit módon felveszi ezt az anoním típust, és úgy használhatjuk, mint bármely hagyományos osztálypéldányt.

        Zárszóként nézzünk egy egyszerű linq-s példát arra, hogy miként tudjuk ezt kihasználni lekérdezésünkben.

        image

        A fenti lekérdezés az  1996 után publikált könyvek között szűr. Ami fel kell, hogy tűnjön, az az, hogy nem "select b" van odaírva, azaz a query változóba nem könyvek egy listája kerül be!!! Hanem select new {...} miatt anoním típusok egy listájával tér vissza a lekérdezés. Az anoním típusunknak két mezője van, az "Author" és a "Title". SQL-es gondolkodással a select b tulajdonképpen egy olyan osztály-t szimbolizál, ami egy teljes sort képvisel. Míg a fent említett projekció "oszlopokat" vág ki nekünk a sorból és egy anoním típusba csomagolja az eredményt. Fárasztó lenne írni minden ilyen projekcióhoz egy megfelelő csomagoló osztályt, nem igaz? Helyette anoním típusok és object initializer-ek segítségével egy roppant rugalmas és átlátható megoldást tudtunk elkészíteni. Természetesen használhattunk volna származtatott értékeket is. Az alábbi példán csak a select rész látható:

        image

        Az új anoním típusunk egyik mezője a könyv címe (Title), a másik a "Price" nevű kalkulált mező pedig a bruttó ára lesz.

        Lekérdezéseinknek innentől kezdve csak a képzelet, nomeg a józan ész szabhat határt. Remélem minden olvasó profitált vmit ebből a kis irományból. Végül pedig megragadnám az alkalmat és BOLDOG ÚJÉVET kívánnék minden kedves olvasónak!

        December 18

        WinForms - Hozzáférés control-okhoz külön szálból

        Bár nem mai technológia a WinForms, mégis sokak futnak abba a problémába, amikor külön szálból szeretnének hozzáférni az egyik control-hoz, programuk egy csúnya hibaüzenettel elszáll. Miért van ez?

        Ha a winforms-os controlunkat több szálból is próbálnánk elérni és mindenféle veszélyesebbnél veszélyesebb dolognak kitenni (ha nem, akkor is) előfordulhat, hogy inkonzisztens állapotba hoznánk, ami megengedhetetlen. A UI külön szálon fut, ezért a .NET Framework csúnyán ránkszól, ha egy másik szálból szeretnénk hozzáférni, ami nem fog menni. Egészen pontosan illegal cross-thread operation hibaüzenetet dob nekünk. Természetesen létezik megoldás a problémára, méghozzá több is, én most az egyik legegyszerűbbet szeretném itt bemutatni.

        Nézzük a problémát. Ez a kis program a gombnyomás hatására egy külön szálat indít, amely egy szöveges üzenetet ír bele a textBox-ba.

        image

        Munkánknak be is érik a gyümölcse, arra a sorra érve, ahol megpróbáljuk beállítani a Text property tartalmát a következő szeretetcsomagot kapjuk:

        image

        A probléma már nem ismeretlen számunkra, kérjünk hozzáférést a controlhoz. Egy lehetséges megoldás a következő: Hozzunk létre egy callback delegate-et, nézzük meg, hogy a textBox-unk InvokeRequired property-je true-e, és ha igen, akkor hívjuk meg a textBox Invoke függvényét a delegate-ünkkel, természetesen sajátmagunkat visszahívva. Ha false, akkor nyert ügyünk van, már írhatjuk is a textBox-ot.

        image

        Természetesen van más megoldás is Backgroundworker-ek használatával és egyéb varázslatokkal. De úgy gondolom a fenti megoldás magáért beszél.

        December 16

        LINQ - Bevezetés a C# 3.0 újdonságaiba (1.)

        A mai fejlesztői világot teljesen átitatja az objektum orientált gondolkodás, tervezés. Sajnálatos módon még ma is meg kell küzdenünk nem objektum orientált adatforrásokkal, mint például az XML, vagy a relációs adatbázisok. Ezt a kellemetlenséget igyekszik a linq elfedni előlünk. A Microsoft fejlesztői kitettek magukért, nyelvi szinten integrálták be ezt a rendkívül rugalmas és hatékony lekérdező nyelvet. Természetesen a gondolat nem újkeletű, gondoljuk csak az nQuery-re vagy az NHibernate-re. A fejlesztői világ reakciója természetesen nem egyhangú, vannak akik rögtön nekiálltak saját linq providerek fejlesztésének, (Linq 2 Sharepoint, Linq 2 Flickr, Linq 2 Google stb...). Hosszútávon mi is a cél? Gondoljunk bele, milyen kényelmes lenne, ha különböző adatforrásokhoz ugyanazt a lekérdező szintaktikát tudnánk alkalmazni, méghozzá C#-ból, vagy Visual Basic-ből. Mindegy, hogy egy SQL Servert vallatok, vagy egy xml fájlban keresek, netán egy Sharepoint listát dolgozok fel. (Aki írt már CAML-t az tudja, miről beszélek) De vannak olyanok is, akik szkepticizmussal teli pillantásokat vetnek rá, ugyanis a linq alaposan átírja az adathozzáférési rétegről(DAL) eddig alkotott elképzeléseinket.

        Node itt az ideje, hogy mások véleménye helyett, saját elképzelést tudjunk alkotni a LINQ-ról. Ezt a technológiát a .NET 3.5 hozta el számunkra, mégpedig C# 3.0-ba illetve VB 9.0-ba ágyazva. Itt most én a C# 3.0-át választottam.

        Az első, amit tudni kell, hogy egy linq lekérdezés bármely olyan adatforrásra rászabadítható, amely megvalósítja az IEnumerable<T> interface-t, azaz felsorolható. Az alábbi példában a PersonList tömbből azokat a stringeket válogatjuk ki, amelyeknek a hossza legalább 4. Ugye mennyire hasonlít az SQL-re? (Pirosponttért el lehet gondolkodni rajta, hogy vajon miért from - select a sorrend és miért nem select-from) Ezt követően a query elemeit egy mezei foreach-csel bejárhatjuk. Ezt a lekérdezés formát nevezik query expression-nek. Könnyen olvastható és érthető.

        image

        A fenti lekérdezést megírhattuk volna így is:

        image

        Ezt nevezzük method-based query-nek. A két lekérdezés teljesen egyértékű. Vizsgáljuk meg egy kicsit jobban ezt a második szerkezetet, ugyanis számos újdonságot rejt számunkra. Először is a PersonList objektum típusa string tömb! Nincs ilyen nevű tagfüggvénye, hogy Where, OrderBy vagy Select. Ezek az úgynevezett extension method-ok. "Később" definiált speciális statikus függvények, melyek kiterjesztik azon objektumok képességeit, melyekre alkalmazhatók. A fenti Where függvény egy extension method, melynek paramétere egy lambda expression. Ez nem más, mint egy egyszerű logikai kifejezés, ami a Where esetében azt mondja, hogy válaszd ki azokat a p-ket, melyekre igaz az, hogy a p hossza legalább 4. Első ránézésre talán kicsit elrettentő de valójában egyszerű dolgoról van szó. Amire érdemes figyelni, hogy ezekkel a nyelvi újdonságokkal, illetve a kód tömörségére törekedve hihetletlenül átláthatatlan kódokat lehet írni.

        Még egy dologra térnék ki anélkül, hogy túl mélyre próbálnék hatolni a C# 3.0 nyelvi újdonságaiban, ez pedig az implicit módon típusos lokális változó, azaz a "var". Sokan fáznak tőle, mert hogy típus nélküliség .net-ben nem szerencsés. Ők tévednek, mert nem erről van szó. A var-ként deklarált változó igenis nagyon erősen típusos, csak éppen implicit módon adom meg a típusát. Azaz, az lesz a típusa, ami az értékadás jobboldalán áll.

        image

        Nézzük meg az alábbi képet. A debug módban futtatott Locals ablakban látható, hogy "a" string, "b" int, "c" pedig DateTime típusú. A változó inicializálásakor implicit módon megadtuk a típusát. Hogy ezt miért jó? Sokszor egyszerűbb, kevesebbet kell gépelni, de a legfőbb indok, hogy ha anonymous type példányt hozunk létre, akkor értékül csak var-ként deklarált változónak tudjuk adni, hiszen nincs neve a típusnak.

        image

        Remélem senkit sem rettentettem el a linq-tól és a C# 3.0-ától, sőt éppen ellenkezőleg, szeretnétek jobban megismerni ezeket az újdonságokat, megérteni, hogy működnek és mire jók. Ez a cikksorozat ebben próbál segítséget nyújtani.

        December 14

        Péntek esti WPF mosoly

        Így pénteken este jobb dologom nem lévén Krisztián kollegám kérdését próbáltam megválaszolni. Ha netántán valaki olyan elvetemült lenne, hogy dinamikusan szeretne hozzáadni elemeket egy ListBox-hoz, és történetesen ezek az elemek stringek és mondjuk ugyanazok a stringek, akkor a listbox bizony megbolondul. Miért van ez, és mit lehet tenni ellene? Különböző stringeknél ez miért nem jelentkezik?

        Egészen konkrétan:

        image

        És akkor SingleSelect módban elkezdem klikkelgetni a listboxot, ami összevissza kiválaszt több elemet, annak ellenére, hogy single módban vagyunk, tiszta káosz az egész.

        Amit tudok, az a megoldás:

        clip_image001[6]

        Ha így adogatjuk hozzá az elemeket, akkor a dolog tökéletesen működik.

        Az Add függvény objecteket vár, tehát nem arról van szó, hogy csak ListBoxItem mehet bele. Az én gyanúm az, hogy a listbox az elemeit a Hashkódjuk alapján különböztetheti meg. Mivel a GetHashCode()-ot a stringek esetén felüldefiniálták oly módon, hogy ha két string tartalma megegyezik, akkor a hashcode-juk is egyezzen meg, így a listbox nem igazán tud mit kezdeni a dologgal. A második esetben a stringeket lényegében egy másik osztályba wrappeltük, konkrétan ListBoxItem-ekbe, így a különbség tétel nem okoz problémát. A dolog elég nagy mosolyt csalt az arcomra, és ez továbbra is csak tipp. Bárkinek szívesen fogadom bármi ötletét, esetleg vki jól reverse engineeringelje meg és akkor majd megtudjuk jól :)

        Szerk.

        A sejtés helyes. WinForms-ban a ListBox az add függvény hívása esetén egy ListBoxItem objektumot hoz létre, így a wrappelés megtörténik. Wpf esetén viszont megőrzi az eredeti típust, így sztringek esetén a hashcode összeakadhat. Special thnx to R1cs1 and Giorgio!