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

2010. július 11., vasárnap

Validációs konstansok generálása T4 segítségével

Úgy gondolom, hogy sokan lehetnek azok, akik utálják, ha valamit kétszer kell beírni. Az egyik kollégám és én biztosan így vagyunk…Főleg, ha szigorúan típusos környezetben vagyunk.

Az egyik ilyen eset amikor validációs attribútumokkal dolgozom. Sok tévedésre ad lehetőséget, ha tagok dekorálása során valamelyik stringet elírjuk.

A probléma

Például az alábbi CustomValidation attribútumnál is meg kell adnunk a validálást végző metódust.

[CustomValidation(typeof(DataValidation), "IsAgeValid")]
public int Age { get; set; }

A probléma nem az, hogy ez a megoldás kényelmetlen, hanem az, hogy ha véletlenül elírjuk a metódus nevét (Pl.: “IsAgevalid”), akkor a projekt gyönyörűen le fog fordulni. A hibát majd akkor fogjuk kapni, amikor ez a validáció lefut. Nyilván ez egy kisebb projektnél nem probléma, de amikor sok 100 meta-adat attribútumról beszélünk, akkor azért nem egyértelmű, hogy minden hibát könnyedén kiszúrunk.

A megoldás(om)

A probléma megoldására kódgenerálást választottam. Azaz előállítok minden validációs metódusra egy string konstanst. Ehhez a Visual Studióba (2008, 2010 nem Express változatok) beépített T4 (Text Template Transformation Toolkit) generátort.

Cél: Legyen egy olyan osztály, amelyben konstansként szerepelnek a validációs metódusok.

public static partial class DataValidation
{
    public const string IsAgeValidMethod = "IsAgeValid";
}

Ezek megléte esetén már így tudunk hivatkozni a metódusokra:

[CustomValidation(typeof(DataValidation), 
DataValidation.IsAgeValidMethod)]
public int Age { get; set; }

 

Így készült

A T4 scriptek egyszerű szöveges fájlok. Így szerkeszthetők bármelyen szövegszerkesztővel, de javaslom a “Tangible T4 Editor” letöltését a Visual Studióhoz. Könnyen elérhető a Tools menüben található Extension Manager használatával.

image

A telepítés után új sablonokat találunk az Add New Item ablakban:

image  A “Simple T4 template” tökéletes az induláshoz: benne lesznek az alapvető import direktívák, a kimenet .cs és egy kis segítség a folytatáshoz.

image

Na én ebből a Windows.Foms-t kidobtam és beraktam inkább a EnvDTE assambly hivatkozást és importot. Az EnvDTE egy olyan library, amivel a Visual Studio automatizációját lehet elérni.

image

A script felépítése igen egyszerű. A bevezetőszöveg után egy namespace, majd minden egyes feldolgozott validációs osztályt tartalmazó fájlhoz generál a ProcessProjectFile hívás egy osztályt.

image Először a script meghívja az Init függvényt, amelyben inicializáljuk a hozzáférést a projekthez:

image

Ezek után a hívódik meg a ProcessProjectFile, amely feldolgozza a megadott fájlt. A feldogozás során a DTE projektmodellen lépkedünk végig rekurzívan. A modell így épül fel: ProjectItem > FileCodeModel > CodeElements.

Végigmegyünk ezeken a CodeElement-eken és azok vizsgálatával generáljuk ki a nekünk szükséges kódot. A CodeElement-ek is tartalmaznak további gyermek CodeElement-eket, ezért rekurzívan végig kell ezeken menni. Az érdemi munkát ez a rész csinálja:

image

A kódgenerálóban nem sokat törődtem a hibakezeléssel. Sokkal komolyabban is meg lehet valósítani ezt a scriptet, most inkább csak a kedvcsinálás volt a cél.

Példa forráskód a CodePlex-en: http://martonrusko.codeplex.com/

Referenciák:

Oleg Sych MVP blogja – rengeteg cikk a T4-ről
Tangible T4 Editor – a T4 editor Visual Studióhoz