Jeg har i dag valgt at lancere det nye design af styrdindiabetes.dk.  Tag godt i mod det.

Godt nytår til jer alle.

Det er lang tid siden forrige indlæg, men der har været travlt med mange andre ting på arbejde og i fritiden.  Det er på tide at komme tilbage “on-track” med styrdindiabetes.dk.

Da jeg fornylig efter lang tids pause genoptog udvikling af styrdindiabetes.dk, virkede login med Facebook/Google/Microsoft pludselig ikke længere i Internet Explorer.  Årsagen er, at jeg i mellemtiden har opgraderet til fra Windows 8 til 8.1 med det resultat, IE er blevet opgraderet til version 11.

En spørgsmål på Mobile Services fora gav et svar fra en hjælpsom MSFT medarbejder: Jeg skulle opgradere Mobile Services JavaScript client library til nyeste version.

Mit forrige indlæg handlede om authorization via Windows Azure Mobile Services.  Authorization består af to elementer:

  1. Kun brugere, der er logget på, må kunne tilgå data.
  2. En bruger må kun kunne tilgå sit eget data.

Sidste gang diskuterede vi punkt 1 – denne gang bliver det punkt 2.

Udfordringen består i at forhindre, at en bruger læser eller redigerer i andre brugeres blodsukkermålinger.  Der er et hav af eksempler på, hvordan man sikrer, at en bruger ikke kan læse andre end sine egne data.  Det kræver blot følgende “where” clause i server-side node.js script for “Read”:

function read(query, user, request) {
    query.where({ userId: user.userId });
    request.execute();
}

Det kniber lidt mere med gode eksempler på, hvordan man f.eks. implementerer “Update” eller “Delete”.  For “Update” valgte jeg at benytte mig af det indbyggede MSSQL objekt, der kan bruges til at eksekvere rå SQL.  Som vi har talt om før, er data backend på Mobile Services blot en SQL Server:

   1:  function update(item, user, request) {
   2:      mssql.query('SELECT TOP 1 Id FROM Entries WHERE Id = ? AND userId = ?', [item.id, user.userId], {
   3:          success: function (results) {
   4:              if (results.length > 0) {
   5:                  item.userId = user.userId;
   6:                  request.execute();
   7:              }
   8:          }});
   9:  }

Idéen er, at jeg først checker, om der findes en entry med det pågældende id for den pågældende bruger.  Er det tilfældet, eksekverer jeg request’en. 

Linje 5 skal sikre, at brugeren ikke forsøger at putte en anden brugers ID på en blodsukkerregistrering.

“Delete” funktionen implementeres tilsvarende.

Vi er dermed klar til at slippe brugerne løs.  De kan logge ind med Facebook, Google eller en Microsoft konto, og de kan oprette blodsukkermålinger på livet løs uden at genere andre.

Authorization er den proces, der foregår, når man styre brugernes adgang til data.  På styrdindiabetes.dk skal det foregå på to niveauer:

  1. Kun brugere, der er logget på, må kunne tilgå data.
  2. En bruger må kun kunne tilgå sit eget data.

Det er punkt 1, vi skal se på i dette indlæg.

Vi har tidligere set på, hvordan Azure Mobile Services har authentication med Facebook o.a. indbygget.  Azure Mobile Services har også indbygget funktionalitet til at styre, hvem der må tilgå de databasetabeller, man opretter i backenden.  Det hele foregår i Azure Management portalen.

For hver tabel oprettet i Mobile Services, findes en Permissions side, hvor man kan angive, hvem der må udføre hver af de 4 CRUD operationer.  Man har følgende fire muligheder:

  • Everyone
  • Anybody with the Application Key
  • Only Authenticated Users
  • Only Scripts and Admins

image

Bemærk at for et website er der i praksis ingen forskel på “Everyone” og “Anybody with the Application Key”, da vores application key står frit tilgængelig i JavaScript’en, så enhver, der kender til højreklik og “Vis kilde” i browseren, kan få fat i nøglen.

Når en bruger via sitet forsøger at tilgå denne tabel vha. client.getTable("Entries"), vil Azure forhindre al tilgang for brugere, der ikke er logget på vha. Facebook eller andet.

I næste indlæg skal vi se på, hvordan vi sikrer, at brugerne kun kan tilgå deres eget data.

Som nævnt bruger Azure Mobile Services en SQL Server som backend database.  Det er ikke en normal SQL Server.  F.eks. findes begrebet foreign keys ikke, og Azure management konsollen giver ikke mulighed for at tilføje kolonner til tabeller som sådan.  I stedet bliver kolonner oprettet dynamisk ud fra det data, man sender til servicen.  Dette er en setting under “Configure” fanen for ens Mobile Service.

image

Husk at slå dynamic schema fra i produktion!

I første omgang har styrdindiabetes.dk behov for en enkelt tabel til at holde de indtastede blodsukkermåling.  En måling består bl.a. af en dato, typen (morgenmad, frokost, aftensmad eller sengetid) og blodsukker før og efter måltidet, hvor det giver mening.  Som JavaScript objekt kunne det se således ud i styrdindiabetes.dk (med lidt knockout.js blandet ind i det):

    var entry =
        {
            type: self.type(),
            beskrivelse: self.beskrivelse(),
            blodsukkerfoer: self.blodsukkerfoer(),
            blodsukkerefter: self.blodsukkerefter(),
            dato: dato,
            antalInsulinEnheder: self.antalInsulinEnheder(),
        };

CRUD operationer foregår vha. en REST API med POST, GET, PUT og DELETE.  Microsoft har heldigvis pakket REST API’en ind i en dejlig JavaScript API.  En CREATE/INSERT/POST operation kan f.eks. se således ud:

    client.getTable("Entries")
        .insert(entry)
        .done(function (result) {
            self.id(result.id);
            // ...
            }
        }, function (err) {
            self.root.errorMessage(err);
        });

JavaScript API’en opbygger HTTP kaldet for os.

Første gang vi laver ovenstående kald til “insert”, vil Mobile Service benytte dynamic schema og tilføje kolonner i Entries tabellen svarende til de properties, jeg definerede på entry objektet ovenfor.

Om  man kan lide dynamic schema er en smagssag.  Man kan godt frygte for integriteten i ens SQL Server database, og at der bliver givet køb på normalisering.  På den anden side øges produktiviteten betragteligt, fordi man kan koncentrere sig om at lave sin applikation og ikke om at designe database og lave datalag etc.  Jeg forestiller mig ikke, at Azure Mobile Services er beregnet til store datatunge projekter.

I næste indlæg skal vi se på, hvordan vi kan bruge login information til at styre adgang til data.

Har man et web site, der kører under https, vil requests til http typisk fejle.  Derfor er det god service overfor besøgende at omdirigere http requests til https.

Jeg faldt over nedenstående til at sætte det op på Azure Cloud Services.  Man tilføjer det til web.release.config, så reglen kun bliver brugt for release build – dvs. typisk ifm. publish til Azure i produktionsmiljø.

<configuration>
  <!-- ... -->
  <system.webServer>
    <rewrite xdt:Transform="Insert">
      <rules>
        <rule name="RedirectToHTTPS" stopProcessing="true">
          <match url="(.*)" />
          <conditions>
            <add input="{HTTPS}" pattern="off" ignoreCase="true" />
          </conditions>
          <action type="Redirect" url="https://{SERVER_NAME}/{R:1}" redirectType="Permanent" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

Som nævnt i forrige indlæg, får man authentication med Facebook, Twitter, Google og Microsoft med i Azure Mobile Services client API’en.  Det hele foregår i gennem det client objekt, vi fik fat i ved at inkludere Azure client JavaScript API’en. 

På min knockout.js viewmodel har jeg kode, der meget ligner følgende:

    self.login = function (providerName) {
        self.errorMessage('');
        client.login(providerName).then(self.afterLogin, function (error) {
            self.errorMessage(error);
        });
    } 

I HTML’en kalder jeg login funktion (gennem knockout.js bindings), hvor jeg angiver providernavnet (fremhævet):

    <ul class="dropdown-menu">
        <li><a data-bind="click: login.bind($data, 'facebook')" href="#">Facebook</a></li>
        <li><a data-bind="click: login.bind($data, 'microsoftaccount')" href="#">Microsoft</a></li>
        <li><a data-bind="click: login.bind($data, 'google')" href="#">Google</a></li>
    </ul>

Det vigtige her er kaldet til client.login(providerName). Det geniale er, at samme kode bruges til alle fire authentication providers: Facebook, Twitter, Google og Microsoft.  Jeg skal bare angive providername, som kan være “facebook”, “google”, “twitter” eller “microsoftaccount”. 

Bemærk at authentication kræver, at man først reigstrerer sig hos de fire udbydere, men det er ret nemt, og Microsoft har lavet en beskrivelse af processen.

Desværre virker ovenstående login metode ikke på Windows Phone.  Det er uheldigt, da et af målene netop var at gøre styrdindiabetes.dk tilgængeligt på mobile platforme.  Problemet er, at IE på Windows Phone ikke understøtter popups, hvilket bruges i login processen.  Jeg har indtil videre valgt at Windows Phone må sejle sin egen sø.  Hvis tiden tillader det, kan der eventuelt komme en Windows Phone app til styrdindiabetes.dk.  Der er muligvis også et workaround på StackOverflow, men jeg har ikke prøvet det, og det løser kun problemet for Facebook.  I så fald skulle jeg lave tilsvarende workarounds for de andre udbydere.  Det er jeg ikke interesseret i at bruge tid på, og hele øvelsen med at bruge Azure Mobile Services skulle netop være at give høj produktivitet uden alt for meget fnidder.

Eftersom jeg har bestemt, at styrdindiabetes.dk skal være en Single Page Application, har jeg brug for en backend, hvor det er nemt at implementere en API, der er nemt at kalde fra JavaScript.  Her er REST med JSON et naturligt valg.

I ASP.NET MVC 4 er det nemt at opbygge en HTTP REST-ish API.  Formålet med relanceringen af styrdindiabetes.dk er dog ikke at opbygge en backend med en relationel database og en API ovenpå.  Det har jeg gjort før, og jeg keder mig allerede bare ved at skrive om det. 

Nej, formålet er at lære noget nyt, og det gjorde jeg heldigvis, da jeg i juni var til TechEd i Madrid.  Her så jeg en (ret poppet) session, hvor Steven Sanderson på 75 minutter bankede en SPA sammen med online booking af taxaer med kort og live pushbacks.  Det gjorde han vha. Windows Azure Mobile Services.

Azure Mobile Services har nogle interessante features.  Server-delen er baseret på node.js.  Databasen er SQL Server, men det er ikke muligt at lave relationer imellem tabeller.  Det kan føles som lidt en skuffelse, men på den anden side er Azure Mobile Services ikke bygget til store databasetunge projekter.  Tabellerne i databasen er dynamiske.  Det betyder, at kolonner dannes automatisk ud fra properties på de objekter, man sender fra klienten (det skal slås fra i produktion!).

Azure Mobile Servies har client API’er til Windows Phone, Android, iOS og ikke mindst til JavaScript.  API’en inkluderer authentication via FaceBook, Twitter, Google og Microsoft, hvilket skal bruges til styrdindiabetes.dk.

Platformen indeholder også andre ting som udsending af mails, push notifications og scheduled tasks.  Det er ikke noget, jeg regner med at få brug for, men man skal aldrig sige aldrig.

Priserne findes på Azure.  Som det fremgår får man 10 tjenester gratis med 20 MB SQL Server.  Det er nok til at lige at komme i gang.

Kode

Efter man har lavet sig en Azure Mobile Service, får man et client application id, som man skal bruge i sin JavaScript:

    <script src="https://styrdindiabetes.azure-mobile.net/client/MobileServices.Web-1.0.0.min.js"></script>

    <script>
        var client = new WindowsAzure.MobileServiceClient(
            "https://styrdindiabetes.azure-mobile.net/",
            "<Indsæt application id her"
        );
    </script>

Igennem client objektet har man nu adgang til hele API’en fra sin Azure Mobile Service, og så er det bare at gå i gang.

Jeg har længe haft dette emne på listen over ting, jeg kunne skrive et par ord om på dotninjas.dk, og så sent som i dag fik jeg en mail fra en kollega, der ikke kunne få et af mine programmer til at køre, fordi programmet fejlagtigt påstod, at nogle filstier på netværkshares ikke fandtes. Problemet var, at han kørte programmet som administrator.

Ironisk nok kan man på Windows 7 komme ud for, at administrator elevation lader til at give begrænsede muligheder, når man tilgår netværkshares.  Dette er en velkendt “feature” i Windows 7, og der findes et workaround.  Løsningen er manuelt at rette i registry, som beskrevet i linket nederst.

En god tommelfingerregel er aldrig at kræve UAC elevation for sine applikationer.  Men selv Visual Studio kræver nogle gange elevation.  Det er f.eks. nødvendigt, når man vil køre ASP.NET web sites på den rigtige IIS (i stedet for IIS Express), eller man ønsker at bruge debugfunktionaliteten attach to process.

I så fald kan det workaround, der er beskrevet i følgende TechNet artikel, være nyttigt:

http://technet.microsoft.com/en-us/library/ee844140(v=ws.10).aspx

Normalt forventer man, at hvis a + b giver c, så vil a + b altid give c.  I programmering skal man have en vis tolerance mht. præcision, når man bruger doubles og floats, men hvis a + b giver c i et program, forventer man samme resultat, uanset hvornår man laver beregningen indenfor samme proces.

Sådan troede jeg det var indtil fornylig, hvor én af vores unit tests fejlede – tilsyneladende lidt tilfældigt.  For kørte jeg den pågældende unit test alene, var der ingen fejl.  Kørte jeg den sammen med de andre unit tests i projektet, fejlede den.  Jeg fik ret hurtigt identificeret, hvilke andre unit tests, der drillede, og den fejlende unit test havde absolut intet med de andre unit tests at gøre.  Der var ingen fælles kode.

For nemheds skyld vil jeg kalde den fejlende unit test for A, og kalde de andre unit tests, som forårsagede fejlen i A for B. 

Test A tester kode indeholdende komplekse beregninger (Nelder-Mead), og fejlen gjorde, at det endelige resultat endte med en difference på 0.5!  Det er meget i min verden.

Test B er lidt speciel.  Den tester funktionalitet til at indlæse data fra et regneark.  Dvs. koden, som B tester, benytter Microsoft’s ACE driver. Efter en del test af denne driver, og noget der ligner to dages riven i mit eget hår, var jeg i stand til at genskabe problematikken med et simpelt eksempel, hvor resultat af et gangestykke med doubles gave forskellige resultater før og efter kald til ACE driveren.

Et spørgsmål på StackOverflow affødte en forklaring: “… unmanaged code may be tinkering with the FPU control word and change the way it calculates”.  Der blev også foreslået en løsning, nemlig et kald til _fpreset, som “resets the floating point package”.

Den foreslåede løsning virker, men jeg føler mig ikke overbevist om, at jeg egentlig har lyst til at bruge ACE driveren direkte i vores produktionskode.  En rådslagning med Daniel (også kendt er på sitet som ne0san) førte frem til en anden løsning, nemlig at spawn en ny process, som indlæser data fra regnearket, og kommunikerer data tilbage til hovedprocessen vha. named pipes (måske et emne for en kommende dotninjas blog).  Det virker.  Om det er en bedre løsning end _fpreset er svært at sige.

Da async/await pattern blev introduceret med C# 5, blev hele .NET frameworket gennemarbejdet, så relevante asynkrone metoder blev awaitable og fik Task som returværdi.

Det gælder ikke Windows Phone API’en.  Specielt gælder det ikke for WebClient, der i .NET 4.5 bl.a. har fået DownloadDataTaskAsync, der returnerer en Task, og dermed er awaitable.  Til Silverlight må man nøjes med DownloadDataAsync, som er baseret på Event-Based Asynchronous Pattern (EAP) gennem DownloadDataCompleted.

I System.Threading.Tasks namespacet findes en klasse kaldet TaskCompletionSource.  Denne klasse er netop beregnet til EAP scenarier, fordi den opretter en task, som man kan sætte op til at vente på, at en event bliver kaldt.  Dette gøres vha. af SetResult og SetException.  Kalder man en af de to metoder, vil den tilhørende Task afsluttes.  Vha. af TaskCompletionSource har jeg lavet følgende awaitable extension metoder til WebClient, som gør det muligt at lave awaitables for hver af de 4 HTTP metoder GET, POST, PUT og DELETE.  Metoderne benytter JSON via Json.NET:

    public static class WebClientExtensions
    {
        public static Task<T> GetAsync<T>(this WebClient client, Uri uri)
        {
            var tcs = new TaskCompletionSource<T>();
            client.DownloadStringCompleted += (s, e) =>
                {
                    if (e.Error == null)
                    {
                        T result = JsonConvert.DeserializeObject<T>(e.Result);
                        tcs.SetResult(result);
                    }
                    else
                    {
                        tcs.SetException(e.Error);
                    }
                };
            client.DownloadStringAsync(uri);
            return tcs.Task;
        }

        public static Task PostAsync<T>(this WebClient client, Uri uri, T item)
        {
            var tcs = new TaskCompletionSource<string>();
            client.Headers["Content-Type"] = "application/json";
            client.UploadStringCompleted += (s, e) =>
            {
                if (e.Error == null)
                {
                    tcs.SetResult(e.Result);
                }
                else
                {
                    tcs.SetException(e.Error);
                }
            };

            string data = JsonConvert.SerializeObject(item);

            client.UploadStringAsync(uri, "POST", data);
            return tcs.Task;
        }

        public static Task PutAsync<T>(this WebClient client, Uri uri, T item)
        {
            var tcs = new TaskCompletionSource<string>();
            client.Headers["Content-Type"] = "application/json";
            client.UploadStringCompleted += (s, e) =>
            {
                if (e.Error == null)
                {
                    tcs.SetResult(e.Result);
                }
                else
                {
                    tcs.SetException(e.Error);
                }
            };

            string data = JsonConvert.SerializeObject(item);

            client.UploadStringAsync(uri, "PUT", data);
            return tcs.Task;
        }

        public static Task DeleteAsync(this WebClient client, Uri uri)
        {
            var tcs = new TaskCompletionSource<string>();
            client.UploadStringCompleted += (s, e) =>
            {
                if (e.Error == null)
                {
                    tcs.SetResult(e.Result);
                }
                else
                {
                    tcs.SetException(e.Error);
                }
            };

            client.UploadStringAsync(uri, "DELETE", "");
            return tcs.Task;
        }

Du kan læse mere om TaskCompletionSource hos Stephen Toub, som er en af de ypperste, når det kommer til parallelisering i .NET.

Jeg har haft stor glæde af de ovenstående WebClient extensions, men min glæde var endnu større, da jeg fornylig læste, at Microsoft arbejder på en port af HttpClient til Windows Phone, og har gjort det tilgængeligt via NuGet.  Jeg har endnu ikke prøvet det, men det virker lovende.