Zoals in m'n vorige post is te lezen ben ik momenteel bezig met het maken van een nieuwe Windows Phone 7 applicatie met het MVVM pattern. Op zich niet zo heel spannend, aangezien er duizenden anderen zijn die dit ook doen.

Enkele jaren geleden heb ik geleerd om m'n applicatie in meerdere logische lagen of tiers op te delen, presentatie-, business- en data-laag. Ik ben nog steeds een groot voorstanden van dit principe en of je hier nu lagen of tiers gebruikt, beide keren moet er data van 1 klasse naar een andere worden gestuurd.

Dit wil ik nu dus ook toepassen binnen m'n Windows Phone 7 applicatie. Eerst heb ik nu een nieuw MVVM Light project gemaakt met daarin de benodigde onderdelen voor m'n applicatie. Daarna heb ik gelijk een business en een data laag er bij gemaakt. Tegenwoordig heb je ook de keuze om een Windows Phone Class Library te maken:

Hier heb ik dan ook voor gekozen, omdat deze er standaard voor zorgt dat ik niet alle assemblies van .NET 3.5 kan kiezen en dus een dll maak die geschikt is voor een WP7 apparaat. Het resultaat is ongeveer als volgt:

Aangezien ik jarenlang aan webdevelopment heb gedaan in ASP.NET en Sharepoint 2007/2010 ben ik niet meer zo in de loop van asynchroon werken en het gebruik van meerdere threads binnen je code. Met Silverlight ontkom je hier niet aan, dus heb ik m'n kennis aardig moeten bijspijkeren. Vandaag heb ik ongeveer de gehele dag besteedt om communicatie tussen m'n lagen aan de praat te krijgen. Na veel voorbeelden te hebben gelezen, geïmplementeerd en verwijderd te hebben, heb ik nu toch iets werkends kunnen maken. Of het helemaal goed is, dat is een tweede. Waarschijnlijk kijk ik over een jaar hier heel anders tegen aan, maar momenteel ben ik er tevreden mee.

Het voordeel van werken met meerdere threads is dat je UI niet vast loopt tijdens het uitvoeren van een actie. Ook kan alleen de UI-thread de UI bijwerken, wat het geheel iets 'veiliger' maakt. Nu was het de bedoeling dat ik aan de hand van de coordinaten van de telefoon (interne GPS) de plaats ging bepalen. Hier kan natuurlijk Bing of Google voor worden gebruikt. Aangezien je bij Bing een key moet aanvragen, heb ik nu eerst voor Google gekozen.

Wat ik wilde doen is de coordinaten van het apparaat doorsturen naar de business laag, hier wordt deze dan worden vertaald naar 2 double's die in de data laag kan worden gebruikt en dan de Google services er mee kan aanroepen. Heel eenvoudig en in een website zou ik dit ook in enkele minuten hebben gemaakt. In Silverlight moest ik me echter gaan houden aan het asynchrone model. Gelukkig kan er tegenwoordig gebruik worden gemaakt van lambda expressies en Action<T> en Func<T>, welke ik nu echt nodig had.

Ten eerste de data laag. Hier plaats ik 2 double's in de code en moet er voor zorgen dat de respons van het http request wordt teruggestuurd. Dit wilde ik doen met het HttpWebRequest. Binnen Silverlight is er nu alleen nog maar de BeginGetResponse methode beschikbaar. Dit was al de eerste aanpassing die ik moest maken in m'n gedachtengang. Na wat zoeken hoe lambda expressies werken (vergeet ik altijd door gebrek aan gebruik) is hier een redelijk goed werkende oplossing voor gemaakt.

Let wel, hier moet nog wel wat validatie en dergelijke op plaatsvinden, maar voor nu werkt het eerst prima.

public void GetNearestTown(Action<GoogleGeoCodeResponse> callback)
{
    var address = string.Format("http://url{0}{1}"this.Latitude, this.Longitude);
    HttpWebRequest webRequest = GetWebRequest(address);
    webRequest.BeginGetResponse(result =>
    {
        var response = webRequest.EndGetResponse(result);
        string resultstring = null;
        Stream responseStream = response.GetResponseStream();
        StreamReader sr = new StreamReader(responseStream);
        resultstring = sr.ReadToEnd();
        GoogleGeoCodeResponse jObject = JSON.Deserialize<GoogleGeoCodeResponse>(resultstring);
        callback(jObject);
    }, null);
}

Het ophalen en lezen van de respons en objecten doe ik nu dus via een lambda expressie binnen de BeginGetResponse. Hier moest ik zelf wel even aan wennen, maar na verloop van tijd wordt het steeds duidelijker. Uiteindelijk plaats ik alle resultaten, via een helper methode, in een zelfgemaakt type. De resultset geef ik hier mee aan een callback, welke als parameter wordt meegegeven. Dit is de eerste keer dat ik nuttig gebruik heb gemaakt van de Action<T>. Ik wist dat het bestond, maar nog nooit het nut er van in gezien, tot nu dus. De callback functie staat namelijk beschreven in de business laag, waar de callback dus ook wordt verwerkt.

Het business object ziet er namelijk ongeveer als volgt uit:

protected string Town { getprivate set; }
private Action<string> responseCallback;

public string GetCurrentCity(GeoPosition<GeoCoordinate> position, Action<string> respCallback)
{
    this.responseCallback = respCallback;
    var gCode = new Geocode(position.Location.Latitude, position.Location.Longitude);
    gCode.GetNearestTown(resultString =>
    {
        GetTownFromResponse(resultString);
    });
    return this.Town;
}
private void GetTownFromResponse(GoogleGeoCodeResponse town)
{
    if (town.status == "OK")
    {
        this.Town = town.results[0].formatted_address;
        responseCallback(this.Town);
    }
}

In de aanroep van de data access (gCode.GetNearestTown) geef ik nu de methode GetTownFromResponse methode mee, welke voldoet aan het Action<string> patroon. Hierdoor zal de callback in deze functie worden uitgevoerd. Zoals is te zien zal deze functie een property in de klasse vullen met de verkregen waarde uit de data laag.
Het vullen van de property is momenteel niet meer direct noodzakelijk, omdat de GetTownFromResponse ook weer een callback functie aanroept welke is meegegeven in de presentatie laag van de code. Immers, in de GetCurrentCity wordt ook weer een Action<string> meegegeven welke een implementatie van een functie bevat welke in de presentatie laag is gedefinieerd.

Het stuk code in de presentatie laag ziet er ongeveer als volgt uit:

private void LocationHasChanged(GeoPosition<GeoCoordinate> position)
{
    Location lb = new Business.Location();
    lb.GetCurrentCity(position, UpdateCurrentCity);
}
private void UpdateCurrentCity(string newCity)
{
    Deployment.Current.Dispatcher.BeginInvoke(() =>
    {
        CurrentCity = newCity;
    });
}

Zoals is te zien wordt de UpdateCurrentCity meegegeven aan de business methode, waardoor deze dus als callback fungeert.

Het stuk Deployment.Current.Dispatcher.BeginInvoke zorgt er voor dat het updaten van de property CurrentCity in de UI-thread gebeurd, zodat dit ook daadwerkelijk wordt uitgevoerd. Wanneer je dit niet goed doet zal er een exception worden ge-raised met als melding "invalid cross-thread access…."

Al met al heb ik weer heel wat geleerd over Silverlight (en .NET) development. De code moet nog wel worden gerefactored, maar het gaat om het idee.

Deze dagen ben ik weer druk doende met een nieuwe Windows Phone 7 applicatie. De vorige applicatie die ik had gemaakt was nogal haastig in elkaar gezet, zonder ook maar een enkel pattern of framework te gebruiken, echt een Hello World dus.

Nu ben ik met een tweede applicatie bezig voor m'n werk en die moet toch van iets hogere kwaliteit zijn. Een collega van me had al een framework gevonden dat ons kon helpen met het MVVM pattern dat veel wordt gebruikt bij Silverlight en WPF applicaties, namelijk MVVM Light. Het duurde even voordat ik alles op het systeem had staan. Het installeren staat goed beschreven op de site van Galasoft: http://www.galasoft.ch/mvvm/installing/manually/
Let er hier ook op dat de hotfix voor Windows Phone 7 ontwikkeling wordt geïnstalleerd:

Important note for Windows Phone developers: If you work with Windows Phone 7, you must install this hotfix on top of V3 SP1.

Wanneer dit niet is gedaan krijg je foutmeldingen tijdens het maken van een nieuw MVVM Light WP7 project.

Zodra de bestanden goed op het systeem staan kan er worden begonnen met ontwikkelen. Er is een nieuw project template beschikbaar, namelijk MvvmLight (WP7).

Dit project bevat gelijk al een tweetal mappen, Model en ViewModel en een XAML bestand.

De ViewModel map bevat ook gelijk al 2 bestanden. Deze zijn belangrijk!

Het bestand MainViewModel.cs is gelinkt aan de MainPage.xaml en de ViewModelLocator.cs bevat volgens mij de logica om ViewModel bestanden te koppelen aan de View's.

Het ViewModel.cs bestand heeft nog niet zoveel nuttige inhoud, zoals is te zien:

public class MainViewModel : ViewModelBase
{
    public string ApplicationTitle
    {
        get
        {
            return "MVVM LIGHT";
        }
    }
    //Nog enkele properties
    public MainViewModel()
    {
    }
}

Omdat ik nog nooit echt met Silverlight, XAML en MVVM heb gewerkt vroeg ik me natuurlijk af hoe het komt dat de properties aan de View zijn gebind. Het grootste nadeel van het gebruik van een framework, is dat je zelf niet alles meer hoeft te maken en dus ook niet precies weet wat er allemaal gebeurd.

Na wat spelen in de XAML ben ik er achter gekomen dat deze regel belangrijk is voor de koppeling met de ViewModel:

DataContext="{Binding Main, Source={StaticResource Locator}}">
												

De StaticResource wordt volgens mij gebind naar de ViewModelLocatorklasse, vanwege de naam.
Het volgende stuk code zorgt er volgens mij voor dat de MainViewModel wordt gebind:

public MainViewModel Main
{
    get
    {
        return MainStatic;
    }
}

Ik heb dit getest door de snippets te gebruiken om nog een Locator aan te maken, genaamd Test.

Daarna heb ik de DataContext gewijzigd in DataContext="{BindingTest,Source={StaticResourceLocator}}">.Nu zag ik de properties van m'n TestViewModel op de pagina weergegeven.

Het lijkt dus dat ontwikkelen met MVVM Light of XAML betekend dat je je aan enkele naam conventies moet houden. Op zich leuk en aardig, maar dat is behoorlijk foutgevoelig natuurlijk. Tijdens compileren kom je hier namelijk niet gelijk achter.

Het toevoegen van acties aan een knop gaat op een vergelijkbare manier:

<Button Content="Zoeken!">
    <Custom:Interaction.Triggers>
        <Custom:EventTrigger EventName="Click">
    <GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding ZoekInDeBuurtCommand, Mode=OneWay}"/>
        </Custom:EventTrigger>
    </Custom:Interaction.Triggers>
</Button>

Sidenote: Hier heb ik de volgende toevoeging voor moeten doen in het <phone:-element, anders kun je geen gebruik maken van de elementen.

xmlns:Custom="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"xmlns:GalaSoft_MvvmLight_Command="clr-namespace:GalaSoft.MvvmLight.Command;assembly=GalaSoft.MvvmLight.Extras.WP7"

In de code van de ViewModel heb ik dan het volgende RelayCommand gedefinieerd als een property:

public RelayCommand ZoekInDeBuurtCommand
{
    get { return new RelayCommand(DoZoekenResultaten); }
}

private void DoZoekenResultaten()
{
    throw new NotImplementedException();
}

Op zich is het geen rocket-science, maar ik had eigenlijk verwacht dat het allemaal iets stricter zou werken. Blijkbaar is dat nog niet het geval. Het was in ieder geval even zoeken naar hoe alles precies werkte en hoe je nieuwe elementen op een pagina kan krijgen. Inmiddels is het me gelukt om een leuke WP7 Pivot applicatie te maken. Nu de inhoud van de pivot's nog.