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.

comments powered by Disqus