2012. október 3., szerda

Ó, de jó ez a TypeScript*

* Ez nem reklámcikk, de olyan… mert imádom.

A Microsoft bejelentett egy új nyelvet a TypeScriptet. Ez nem egy önálló programozási nyelv, hanem inkább egy hiánypótló kiegészítés a Javascripthez.

Amióta a Microsoft komolyan veszi és első számú platformként kezeli a Javascriptet, azóta nagyon hasznos dolgok születnek, amelyek megkönnyítik a fejlesztők mindennapjait. Na, de a TypeScript túl megy minden várakozáson. Egyszerűen fantasztikus.

Ha nagyon gyorsan akarnám összefoglalni, hogy mi ez, akkor a következő funkciókat sorolnám fel:

  • Namespace-eket használhatunk (module)
  • Interfészeket definiálhatunk
  • Osztályokat definiálhatunk
  • Típusos tulajdonságok, fieldek
  • Függvény szignatúrák (típusosan definiálható függvények)
  • Szerkesztési időben validálja a kódot
  • Fordítási hibát kapunk, ha hibás a kód

A felsorolt dolgokhoz semmilyen extra kiegészítőre nincs szükség a futtatáskor. Egyszerű Javascript a kimenet. A fordító megcsinálja helyettünk a sokszor fájdalmasan nehézkes Javascriptes trükköket.

Végignéztem a demó videót a Channel 9-on és nagyon megtetszett a dolog. Azonnal ki is próbáltam.

A Visual Studio 2012 kiegészítés letölthető a http://www.typescriptlang.org/#Download oldalról.

A kivételezésen látszik, hogy ez nem egy háttértermék. Teljes körű Visual Studio 2012 integráció, amely nagyon komolyan működik. Szerkesztési időben figyelmeztet a hibákra, azonnal kiértékeli a kódot, láthatóak az egyes elemek típusai, ha föléjük visszük az egeret. Továbbá refaktorálási műveleteket is támogat.

Kapunk egy project template-et: “HTML project with TypeScript”, amely – véleményem szerint – egy igen butácska kis project, amelyben van egy ts, egy html és egy css fájl. Nyilván demózni jó ez az összeállítás, de azért egy ASP.NET MVC4-es projekt talán jobb kiindulásnak tűnik. Természetesen van új item template is: “TypeScript fájl” néven. Ezt be tudjuk szúrni az MVC4-es projektünkbe is, ami nagyon ígéretes!

Néhány általános jellegű probléma megoldása

Egy haszna azért volt a “gyári” projektnek, mert az első adódó problémát gyorsan meg tudtam vele oldani. Nevezetesen:

Probléma #1: Nem generálja le a TypeScript (.ts) forrásból a Javascript (.js) kódot (csak az első alkalommal)…

Megoldás: Szerkesszük a projekt fájlt (.csproj) és az utolsó zárótag elé írjuk be a következőt:

<Target Name="BeforeBuild">
  <Exec Command="&amp;quot;$(PROGRAMFILES)\Microsoft SDKs\TypeScript\0.8.0.0\tsc&amp;quot; @(TypeScriptCompile ->'&quot;%(fullpath)&quot;', ' ')" />
</Target>

Ó igen, ezek után már generálja a .js fájlokat.

Probléma #2: Ááá, nem működik a jQuery…

Megoldás: Le kell tölteni a TypeScript forráskódját innen: http://typescript.codeplex.com/SourceControl/BrowseLatest. Egy fájlra lesz szükség: typings\jquery.d.ts, amelyet be kell húzni a projektbe és referenciázni a ts fájlból.

Amúgy a forráskód csomagban van egy halom példa, többek között egy jQuery is.

Probléma #3: Nem “enged” meghívni globális javascript függvényeket (amelyek másik korábbi .js fájlban vannak definiálva).

Megoldás: A TypeScript-nek pont az a lényege, hogy csak olyan dolgokat érünk el, amelyeket pontosan definiátunk. A Microsoft készített sok ezer soros definíciós fájlokat, amelyek lefedik a Javascript nyelvet általáben, a jQuery-t (1.7.1-et), a WinRT és a WinJS funkcionalitást. Ha egyéb libraryt akarunk használni, akkor definiálnunk kell azokat az elemeket, amelyekre hivatkozni akarunk.

Példul, ha van egy ilyen függvényünk:

function loadScript(scriptName, callback) {
    ...
}

…és meg szeretnénk hívni TypeScriptből, akkor definiálnunk kell legalább így (ha nem akarunk sokat foglalkozni vele… Ezt nem ajánlom.)

declare var loadScript;

Így működni fog, de bármilyen művelet engedélyezett lesz az elemen. Igazából nem fogunk tudni róla semmit, ez gyors, de nagyon rossz megoldás.

Definiálhatjuk így (kicsit határozottabban…):

declare function loadScript(scriptName, callback?);

Nem rossz, de nem szigorúan típusos.

Vagy teljes körűen:

declare function loadScript(scriptName: string, callback: ()=>any);

Ez így csodás.

Probléma #4: Nem tudom hogyan hívhatok meg egy jQuery extension-t.

Megoldás: Ki kell egészíteni a JQuery vagy JQueryStatic interfészeket. Példul, ha használni szerentnénk a jQuery Cookie plugint, akkor definiálnunk kell a következőt:

interface JQueryStatic {
    cookie(key: string, value: string, options?);
    removeCookie(key: string, options?);
}

Nagyon ígéretes ez a projekt. Próbálgatom tovább. Hamarosan megosztom a tapasztalataimat.

Referenciák:

2011. szeptember 25., vasárnap

Gyorsan válaszoló Azure Web Role Application

Sokak számára okoz fejfájást a IIS recycling. Kis idő után az alkalmazásunk leáll, majd egy új user request éleszti. Ez kisebb forgalmú web alkalmazásoknál egész kellemetlen lehet. Egy kis cache töltögetés, vagy hasonló tevékenység őrületbe kergeti a kedves felhasználót a túloldalon. Engem is…

Érdekes, hogy mennyire jól konfigurálható, hogy az alkalmazásunk milyen szabályok teljesülése esetén legyen leállítva. Ezzel szemben nincs semmilyen normális infrastruktúra arra, hogy az alkalmazás újra el is induljon. Pontosabban a IIS 7.5-ig nem volt.

Ez az IIS verzió a Windows Server 2008 R2-ben debütált, így ha használni akarjuk a WebRole-unkban az új lehetőséget, akkor mindenképpen R2 operációs rendszert kell hozzárendelnünk a Role-hoz.

A fent említett szolgáltatás az “Auto-Start” elnevezésre hallgat, ami máris segít, hogy elkeveredjünk és esetleg összetévesszük az application pool AutoStart beállításával. Nagyon jó a megoldás, mert nem úgy dolgozik, hogy a recycling lenyomja az appunkat, hanem előbb meghívja a warm-upot egy új worker processben, majd ha az véget ér, akkor állítja le a korábbi workert. ScottGu rövid bemutató cikkét elolvasva arra jutottam, hogy ezt pedig én beállítom az Azure alkalmazásomra is.

Az IIS beállításainak módosítására több út is van. Ezek közül csak a programozott eljárást mutatom be.

A cikket elolvasva látható, hogy nem bonyolult a funkció életre keltése, egyszerűen csak editáljuk az applicationHost.config fájlt, implementáljuk a WarmUp kódot és minden príma… Na igen, de azért az Azure-ban ilyeneket nem csinálunk...

Az alábbi kód egy komplett megoldás arra, hogy a WebRole egyik Site-ját Auto-Start-ossá tegyük. De szép mondat!

Gyors lépésről lépésre áttekintés a megoldás használatához.

  1. A WebRole projekthez hozzá kell adni az alábbi referenciát:
    %WindowsDir%\System32\inetsrv\Microsoft.Web.Administration.dll. Ez az assembly tartalmazza az IIS adminisztrációhoz szükséges logikát.
  2. Be kell állítani a Microsoft.Web.Administration referencia Copy Local projekt tulajdonságát True-ra.
  3. Minden ServiceConfiguration fájlban be kell állítani a ServiceConfiguration elemben a osFamly attribútumot 2-re. Ez azt jeleni, hogy Windows Server 2008 R2 rendszeren fog futni a role.
    <ServiceConfiguration serviceName="FastAzureApp" xmlns="" 
                          osFamily="2" osVersion="*">
  4. Ugyanebben a konfigurációban be kell állítani a következőt, hogy legyen jogunk “állítgatni”:
    <Runtime executionContext="elevated" />
  5. Hozzá kell adni a WebRole-hoz egy osztályt, amely a warm up során lesz példányosítva és meghívva. A példában FastMvcWebRole.Helpers.WarmUp
  6. Meg kell valósítani a WarmUp osztályban a IProcessHostPreloadClient interfészt.
    public class WarmUp : IProcessHostPreloadClient
    {
        public void Preload(string[] parameters)
        {
            Trace.WriteLine("Warm up.");
     
            //Inicializáló kód jöhet ide...
     
            Trace.WriteLine("Warm up done.");
        }
    }
  7. Az alábbi metódust hozzáadva a WebRole osztályhoz, beállítható az IIS Auto-Start feature:

    /// <summary>
    /// Az site és az application pool beállításainak módosítása az IIS konfigurációkban
    /// </summary>
    /// <param name="siteName">A paraméter ServiceDefinition.csdef fájl Site name-re utal</param>
    /// <param name="keyName">A serviceAutoStart elem egyedi kulcsa (IIS szinten)</param>
    /// <param name="methodReference">A Warm Up metódus referenciája "Namespace.Metodus, Assembly" formátumban</param>
    private void SetupIIS(string siteName, string keyName, string methodReference)
    {
        using (var serverManager = new ServerManager())
        {
            var siteRealName = string.Format("{0}_{1}", RoleEnvironment.CurrentRoleInstance.Id, siteName);
     
            var site = serverManager.Sites[siteRealName];
            if (site == null)
                throw new Exception(String.Format("Site not found: {0}", siteRealName));
     
            var siteApplication = site.Applications.FirstOrDefault();
            if (siteApplication == null)
                throw new Exception(string.Format("Site Application (first) not found:{0}", siteRealName));
     
            var appPoolName = siteApplication.ApplicationPoolName;
            var appPool = serverManager.ApplicationPools[appPoolName];
     
            //Egy óra tétlenség után legyen recycle
            appPool.ProcessModel.IdleTimeout = TimeSpan.FromMinutes(60);
     
            //Periodikusan is legyen recycle
            appPool.Recycling.PeriodicRestart.Time = TimeSpan.FromHours(29);
     
            //Tegyük az Application Pool-t Always Running módba
            appPool["startMode"] = "AlwaysRunning";
     
            //Állítsuk be a site-on is az AutoStart szolgáltatás engedélyezését
            siteApplication["serviceAutoStartEnabled"] = true;
     
            //Adjuk meg az AutoStart providerünk kulcsát
            siteApplication["serviceAutoStartProvider"] = keyName;
     
            //Adjunk hozzá egy elemet (saját providerünk) az serviceAutoStartProviders kollekcióhoz
            var config = serverManager.GetApplicationHostConfiguration();
            var serviceAutoStartProvidersSection = config.GetSection("system.applicationHost/serviceAutoStartProviders");
            var serviceAutoStartProvidersCollection = serviceAutoStartProvidersSection.GetCollection();
     
            var serviceAutoStartProviderElement =
                serviceAutoStartProvidersCollection.SingleOrDefault(f => f.ElementTagName == "add" && (string)f.Attributes["name"].Value == keyName);
     
            if (serviceAutoStartProviderElement != null)
                serviceAutoStartProvidersCollection.Remove(serviceAutoStartProviderElement);
     
            serviceAutoStartProviderElement = serviceAutoStartProvidersCollection.CreateElement("add");
            serviceAutoStartProviderElement["name"] = keyName;
            serviceAutoStartProviderElement["type"] = methodReference;
            serviceAutoStartProvidersCollection.Add(serviceAutoStartProviderElement);
     
            serverManager.CommitChanges();
        }
    }
  8. Hívjuk meg a SetupIIS metódust az OnStart override-olt metódusból.
    public override bool OnStart()
    {
        SetupIIS("Web", "FastMvcWebRoleWarmUp", 
            "FastMvcWebRole.Helpers.WarmUp, FastMvcWebRole");
     
        return base.OnStart();
    }

Ennyi az egész.
A forráskód elérhető a CodePlex-en: http://martonrusko.codeplex.com címen, vagy letölthető innen:

2011. szeptember 6., kedd

Azure Table storage a gyakorlatban

Aki ismerkedik az Azure-ral – még, ha régi motoros is – óhatatlanul szembesül a klasszikus iskolai kérdéssel: Mi az a …, és mire használható. Ilyen “jelenség” az Azure Table Service is, ami a 3 újfajta tárolási lehetőség egyike.

Az ismerkedés első napján máris sikerült kifektetni a Development Storage Emulátort. Órákig refaktoráltam a kódot, próbáltam kitalálni, hogy vajon mi lehet az oka, hogy az emulátor nem válaszol (!) semmilyen kérésre egy entitás beszúrása után. Pontosabban fogalmazva: egy sikertelen beszúrás után, amelyre a teljesen üres táblát tartalmazó emulátor válasza, hogy

System.Data.Services.Client.DataServiceClientException: 
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<error xmlns="http://schemas.microsoft.com/ado/2007/08/
dataservices/metadata
"> <code>InternalError</code> <message xml:lang="hu-HU">Server encountered an internal error.
Please try again after some time
.</message> </error>

Először megszüntettem minden öröklődést. Már csak egy darab árva entitásom volt, amely egyszerűbb volt, mint a Hello World példa, mégis a fenti üzeneteket kaptam, és a storage emulátor többet nem volt képes válaszolni. Egyszerűen meghalt. Az Azure Storage Explorer a következőt tudta csak mondani:

image 

Minden ilyen próbálkozás után használhatatlanná vált az egész development storage emulator. Szerencsére van egy ilyen gomb: Reset…

image

A gombot megnyomva “lenullázhatjuk” az egész storage-ünket…Azért ez egy éles alkalmazásnál nem nyugtatna meg… Szerencsére csak fejlesztgetünk egyelőre.

Lényeg, hogy a sok-sok megkötés mellett van még egy: a storage emulátor nem bírja a DateTime.MaxValue értéket… Ennek az oka alapvetően egyszerű: a Table Service UTC dátumokkal dolgozik. Így, ha egyszerűen a MaxValue értéket állítjuk be a változó értékének, akkor a DateTime.Kind tulajdonsága Unspecified lesz, azaz meghatározatlan. Ilyen esetben az időt konvertálni fogja a rendszer UTC-be. Ami ugye ilyen módon később van, mint a megengedett maximum.

Az eset rém egyszerűen elkerülhető, ha az alábbi módon adjuk meg a maximum dátumot: DateTime.MaxValue.ToUniversalTime(); Illetve általában figyelnünk kell arra, hogy minden dátum át lesz konvertálva UTC-be. Bár ez nem hiszem, hogy a MaxValue érték 2 órás körzetén kívül bárkit is zavarna Magyarországon.

Az ilyen apróságok mellett a véleményem továbbra is az, hogy az  Azure Table Storage nagyon jó dolog. Erőltetem is rá a fejlesztést, annak ellenére, hogy még egy kicsit még fapados.

2011. szeptember 2., péntek

Azurálódás

Nem gondoltam volna másfél évvel ezelőtt, amikor egy konferencián először hallottam a Windows Azure-ről, hogy egyszer csak én is azt fontolgatom, hogy használjam. Sőt, ma már úgy érzem, hogy kifejezetten versenyképes.

Nyilván nem lehet összevetni olyan szolgáltatásokkal, amelyek évi 10-15 ezer forintért kínálnak “99.9999%-os” rendelkezésre állású tárhelyeket, majd úgy kifekszenek mint a MÁV jegyértékesítő rendszere az 1 db szerverével.

Szóval, belevágtam és előfizettem. Elég sok mindent meg kell ismerni ahhoz, hogy az ember kijelenthesse, hogy tudja, érti az Azure-t.

Nagyon érdekes érzés, hogy mint fejlesztő, egészen másképp látom a világot, ha az Azure-rel dolgozom. Elengedhetetlen, hogy az ember kicsit üzletemberként gondolkozzon, amikor nekikezd egy probléma technikai megoldásához.

Ha az offline világban dolgozunk, előfordulhat, hogy lazán kezeljük a könnyen elérhető erőforrásokat. Ilyen példul az SQL Server adatbázis. Ugye az Express változatok megjelenése óta még arra is SQL-t használunk, amire nem kellett. Nos, ez a Azure világában kicsit másképp néz ki. Itt 1 GB SQL adatbázis 9.99 USD/hó, 5 GB pedig 49,95 USD/hó. Hoppá. Mindjárt beindulnak az optimalizációs gondolatok a fejben…

Beugrik az embernek, hogy vannak itt egyéb lehetőségek is. Például: Mi is az a Azure Table Service? – kérdezi magától… Megnézi az árát, és egyből úgy érzi, hogy esetleg bizonyos dolgokat csak ebben kellene tárolni, mert itt az 1 GB storage ára csak 0,15 USD. De egyből oda is kell figyelni ismét, mert OK, hogy olcsó a store, de van ám itt tranzakciós díj is: 0,01 USD/10.000 tranzakció. Na, az meg mi a…

Szóval ez egy más világ. Érdemes sokat olvasgatni, de ha van egy kis pénzed, annál érdekesebb kipróbálni. Igen részletesen számláznak. Itt nem duma, hogy mindenért fizetsz Mosolygó arc Itt egy screenshot a számlázási elszámolásról…

Azure_billing

Érdemes részletesen megismerni a számlázási egységeket, illetve az azokkal kapcsolatos okosságokat. 1-2 sor kód cseréje komoly megtakarításokhoz vezethet. Éppen ezért nem csak a vezetőknek érdemes ismerni a számlázási adatokat, hanem a fejlesztőknek is kötelező.

Például a fent említett Table megoldás esetén 100 updatet beküldhetünk úgy, hogy az 100 tranzakcióként jelenjen meg a számlán, és úgy is hogy 1 árva darabként. Tényleg másképp kell gondolkodni.

Az alábbi blogbejegyzés részletesen taglalja a számlázás mikéntjét. Ajánlom mindenkinek:

Brad Calder: Understanding Windows Azure Storage Billing – Bandwidth, Transactions, and Capacity

Ez nem a legolcsóbb szolgáltatás, ha mindent nézünk. Nem is arra lett kitalálva, hogy egy 3 oldalas weblapot tegyünk ki, amit 100-an látogatnak évente. Ezt a szolgáltatást azzal az esettel lehet összevetni, amikor legalább egy dedikát virtuális gépre van szükségünk. Itt már érezhetően versenyképes. Ha saját szerverüzemeltetésben gondolkodunk, akkor meg szerintem előnyösebb. Érdemes számolgatni.

Illetve nem csak számolgatni, hanem belegondolni abba, hogy mekkora lehetőség van egy ilyen comboboxban:

Azure_choose_region

Néhány kattintással határozzuk meg, hogy hol működjön az alkalmazásunk, SQL Serverünk, vagy a storage-ünk. Nem kell foglalkozni semmilyen adminisztrációval, rendeléssel, operációs rendszer menedzsmenttel, csak az alkalmazásunkkal.

Érdekes aspektus az is, hogy olyan dolgokat tudunk kipróbálni, amelyeket egyszerűen nem vennénk meg, mert túl drága. Itt a minden szolgáltatás elszámolása használat alapú. A számítás óránként, míg a storage a napi átlagok alapján számolódik. De az SQL Server is ilyen. Szóval, ha 1 napig kell egy adatbázis, akkor egy napot fizetsz.

Érdekes, hogy ha egy új Hosted Service-t hozol létre (kvázi ez egy virtuális gép), akkor kb 3-4 perc és rendelkezésre áll a gép, saját fix IP címmel. Ez utóbbi nagyon tetszett. Ha mondjuk tényleg csak kipróbálni akarod, akkor egy ExtraSmall egység óránkánt 0,15 USD. 1-2 óra alatt kipróbálod, majd törlöd a service-t. Ezzel szemben egy VPS szolgáltatónál ki kell fizetned legalább 1 hónapot. Persze előbb meg kell keresni az optimális szolgáltatót, regisztrálni, kifizetni az egy hónapot, várni a szolgáltatás aktiválására, majd belevágni az ismeretlenbe… Ugyanezt az Azure a fenti 1 db dialogbox kitöltésével megoldja.

Le vagyok nyűgözve az Azure lehetőségeitől. Ajánlom mindenkinek! A jövőben megosztom a további tapasztalataimat is.

Referenciák:

Azure kezdőlap
Windows Azure Pricing Calculator
Brad Calder: Understanding Windows Azure Storage Billing – Bandwidth, Transactions, and Capacity

2011. augusztus 31., szerda

Default Browser Switcher tool VS2010-hez

A napokban fedeztem fel egy remek kiegészítőt a Visual Studio 2010-hez: WoVS Default Browser Switcher.

Ez a kis eszköz egy rém egyszerű problémát old meg, amelyet a Visual Studióban mindig csak kínszenvedéssel lehetett végrehajtani: kiválasztható, hogy a futtatás melyik browserben történjen meg. Például egy MVC 3 projektben, amelyben nincs  aspx file, nem is tudom hogy lehet a “Browse With…” menüpontot elérni. Ha már valahogy elértük, akkor már csak egy build (…) és meg is jelenik a browserválasztó ablak, ahol meghatározhatjuk a default browsert is.

Ez a megoldás azért szimpatikusabb:

image

Az eszköztáron megjelenő ikonok egyértelműek és tökéletesen működik is a plug-in. Nagyon like!

2011. július 27., szerda

Engedélyezhetetlen WPF control ElementHostban?

Ma volt egy igen kellemetlen debug-maratonom. Ki kellett deríteni, hogy vajon miért nem működik két, szinte teljesen azonos WinForms control, amiben egy ElementHost tartalmazza ugyanazt a WPF control-t. Konkrétan az egyik olyan volt, mintha le lett volna tiltva. Nem fogadott semmilyen inputot, se egér, se billentyűzet. Egyértelműen úgy nézett ki, hogy a control disabled állapotban van.

Azt gondoltam, hogy biztos a controlok engedélyezésével lesz valami gond, de nem. Logikailag minden helyes volt. Az összes control engedélyezve volt a hierarchiában.

Nos, egy kis Google keresgélés után kiderült, hogy nem a programmal van a baj, hanem az ElementHost egyszerűen nem szinkronizálja az engedélyezést. Ha a control létrehozásakor az ElementHost vagy annak valamelyik szülője le van tiltva (Enabled = false) akkor az “örökre” le is lesz tiltva.

A megoldás egyszerű. A Microsoft alábbi knowledgebase cikkéből kiderül a megoldás: csinálni kell egy wrappert, amely szinkronizálja az engedélyezést:

public class MyElementHost : ElementHost
{
    protected override void OnEnabledChanged(EventArgs e)
    {
        SynchChildEnableState();

        base.OnEnabledChanged(e);
    }

    private void SynchChildEnableState()
    {
        IntPtr childHandle = GetWindow(Handle, GW_CHILD);
        if (childHandle != IntPtr.Zero)
        {
            EnableWindow(childHandle, Enabled);
        }
    }

    private const uint GW_CHILD = 5;

    [DllImport("user32.dll")]
    private extern static IntPtr GetWindow(IntPtr hWnd, 
uint uCmd); [DllImport("user32.dll")] private extern static bool EnableWindow(IntPtr hWnd,
bool bEnable); }

2010. július 28., szerda

Univerzális View Model készítése 1. rész

Az MVVM (Model-View-View Model) tervezési minta használata nagyon célszerű üzleti alkalmazások fejlesztése során. Egyrészt igen átláthatóvá teszi a felület működését, másrészt remekül lehet modularizálni a rendszert. Továbbá megsokszorozódik a jelentősége, ha több platformra kívánunk GUI-t fejleszteni.

Ebben a cikkben megosztom gyakorlati tapasztalataimat a legújabb technológiák használatával összeállított ViewModel alaposztály elkészüléséről. Fontos, hogy olyan alaposztályokat akartam létrehozni, amelyet cross-platform tudok alkalmazni (pl. .NET 4 és Silverlight 4 környezetben is).

A ViewModel funkciója, hogy biztosítsa a View (pl. egy Silverlight UserControl) számára az adatokat, amelyek az adatkötéssel (binding) ér el. Továbbá, hogy reagáljon a modellben történt adat- és állapotváltozásokra.

Ahhoz, hogy a binding működjön, szükséges, hogy a modell implementálja a INotifyPropertyChanged interface-t, amely biztosítja a modellre “csatlakozóknak” az értesítést a modell változásairól. Az alaposztályunk:

public abstract class ViewModelBase : INotifyPropertyChanged
{
    // code...
}

Hagyományosan úgy valósítjuk meg az értesítést, hogy a tulajdonság set metódusába beleírunk egy hívást, amely a meghívja a PropertyChanged eseményt:

IEnumerable<Partner> partners;

public IEnumerable<Partner> Partners
{
    get { return partners; }
    set
    {
        partners = value;
        RaisePropertyChanged("Partners");
    }
}

Ezzel a megoldással nincs semmi gond – szintaktikailag. Látható, hogy elég körülményes lenne minden egyes tulajdonság definiálása a fenti módszerrel, ráadásul magában hordozza a tévesztés lehetőségét, hiszen a tulajdonságunk nevét string konstansként kell megadnunk. Ha refaktorálunk, akkor elveszhet a szinkron. Diplomatikusan még elmondható, hogy az áttekinthetőséget sem javítja.

A probléma orvoslására a PostSharp programot használtam, amellyel úgynevezett AOP valósítható meg, azaz aspektus orientált programozás. A PostSharp bemutatkozó oldalán olvasható, hogy a komplikált teória elmagyarázása helyett mutatnak néhány példát. Én is inkább ezt teszem.

A PostSharp használata általában egy attribútum definiálásával kezdődik. Ezt valamelyik a PostSharp library-ben található aspektus attribútum osztályból származtatjuk.

A fent meghatározott problémára remek válasz lenne egy olyan aspektus implementálása, amely a tulajdonság változásakor fut le. Szerencsénk van, mert létezik ilyen! Úgy hívják: LocationInterceptionAspect. Nem nagyon értem, hogy miért ez a neve, de legyen. (Itt szeretném megjegyezni, hogy az INotifyPropertyChanged problémára van a PostSharphoz remek példakód, amelynek csak egy apró hátulütője van, hogy az ingyenes verzióval nem lehet lefordítani... Természetesen, aki megvette a programot, annak az a megoldás sokkal elegánsabb, hiszen csak az osztályt kell attributálni, és mindent megy “magától”.)

Két virtuális metódust tartalmaz a LocationInterceptionAspect aspektusosztály. OnGetValue, OnSetValue. Nos, nekünk az utóbbira van szükségünk. Azt szeretnénk, hogy ez a metódus meghívja a ViewModel osztály RaisePropertyChanged metódusát. Mivel tudnia kell, hogy implementál a ViewModel egy ilyen metódust, de azért nem kellene, hogy az publikus legyen, ezért egy interfészt vezetünk be, amelyet explicit valósítunk meg:

public interface ISupportRaisePropertyChanged
{
    void RaisePropertyChanged(string propertyName);
}
public abstract class ViewModelBase : INotifyPropertyChanged, 
ISupportRaisePropertyChanged
{
    protected void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged.Invoke(this,
new PropertyChangedEventArgs(propertyName));
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

    #region ISupportRaisePropertyChanged Members

    void ISupportRaisePropertyChanged.RaisePropertyChanged(string 
propertyName)
    {
        ((ISupportRaisePropertyChanged)this)
.RaisePropertyChanged(propertyName);
    }

    #endregion
}

Remek. Most már meg tudjuk hívni a RaisePropertyChanged metódust az aspektus OnSetValue metódusából:

public sealed class NotifyPropertyChangedAspectAttribute 
: LocationInterceptionAspect
{
    public override void OnSetValue(LocationInterceptionArgs args)
    {
        base.OnSetValue(args);

        if (typeof(ISupportRaiseProperyChanged)
.IsAssignableFrom(args.Instance.GetType()))
        {
            var target =
((ISupportRaiseProperyChanged)args.Instance);
            target.RaisePropertyChanged(args.LocationName);
        }
    }
}

Ezzel a megoldással elértem, hogy az alábbi egyszerű formában lehessen definiálni a View Modellünk tulajdonságait:

[NotifyPropertyChangedAspect]
public IEnumerable<Partner> Partners { get; set; }

Ez azért így jobb. Tévedések is kizárva. Ez a kód fordul .NET 4 frameworkkel és Silverlight 4-el egyaránt.

Folytatom…

Referenciák:

MVVM – Model-View-View Model “startlap” Josh Smith MVVM Guru-tól
PostSharp – Aspektus orientált programozási keretrendszer .net-hez