05 August 2013 Async, HowTo, JSON.NET, Windows Phone 8 Robert Muehsig

Man kommt immer recht schnell zu dem Punkt an dem man “irgendeinen” Datenspeicher brauch. Da ein normale normale Text-Datei meist zu wenig bietet und Sqlite auf den ersten Blick auch nicht gerade “schlank” ist nehmen wir einfach eine JSON-Datei.

Trivale Beispielstruktur:

Wir möchten in der Demo-Anwendung einfach eine Liste von diesen Objekten speichern:

    public class Data
    {
        public Guid Id { get; set; }
        public string Value { get; set; }
    }

 

Über diese “API” erzeugen wir einen neuen Datensatz, lesen die komplette Liste aus, hängen das neue Element dran und speichern es erneut. Vermutlich gibt es effizientere Wege dies zu machen – allerdings ist mir im ersten Moment keine einfachere Lösung eingefallen:

private async Task<List<Data>> CreateNewEntry()
        {
            Data demo = new Data();
            demo.Id = Guid.NewGuid();
            demo.Value = Guid.NewGuid().ToString();

            var existing = await Load();

            if (existing == null)
            {
                existing = new List<Data>();
            }

            existing.Add(demo);

            await Save(existing);

            return existing;
        }

JSON.NET, async & Streams

Um die Daten zu schreiben und zu lesen bedienen wir uns der “WinRT”-angehauchten APIs von Windows Phone 8 und eine Menge async & await Code und zu guter Letzt JSON.NET:

private async Task Save(List<Data> data)
        {
            IStorageFolder applicationFolder = ApplicationData.Current.LocalFolder;

            IStorageFolder storageFolder = await applicationFolder.CreateFolderAsync("data", CreationCollisionOption.OpenIfExists);

            string fileName = "data.json";

            IStorageFile storageFile = await storageFolder.CreateFileAsync(fileName, CreationCollisionOption.OpenIfExists);

            using (Stream stream = await storageFile.OpenStreamForWriteAsync())
            using (var sw = new StreamWriter(stream))
            using (JsonWriter jw = new JsonTextWriter(sw))
            {
                jw.Formatting = Formatting.Indented;

                JsonSerializer serializer = new JsonSerializer();
                serializer.Serialize(jw, data);
                await stream.FlushAsync();
            }
        }

        private async Task<List<Data>> Load()
        {
            IStorageFolder applicationFolder = ApplicationData.Current.LocalFolder;

            IStorageFolder storageFolder = await applicationFolder.CreateFolderAsync("data", CreationCollisionOption.OpenIfExists);

            string fileName = "data.json";

            IStorageFile storageFile = await storageFolder.CreateFileAsync(fileName, CreationCollisionOption.OpenIfExists);

            using (Stream stream = await storageFile.OpenStreamForReadAsync())
            using (var sr = new StreamReader(stream))
            using (JsonReader jr = new JsonTextReader(sr))
            {
                JsonSerializer serializer = new JsonSerializer();
                var result = serializer.Deserialize<List<Data>>(jr);
                
                return result;
            }
        }

“Access Denied Exception” – UI trotz async “sperren”

Die Aktion wird über einen Button-Click angestossen. Trotz der ganzen Asynchronität muss man trotzdem darauf achten dass im Grunde nur ein Thread die Methode aufruft – andernfalls kann es zu “Access Denied Exceptions” führen. Der einfachste Weg dies zu bewerkstelligen ist dieser:

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            CreateNewEntryButton.IsEnabled = false;
            try
            {
                var result = await CreateNewEntry();
            }
            finally
            {
                CreateNewEntryButton.IsEnabled = true;
            }
        }

Die UI bleibt allerdings trotzdem völlig reaktionsfähig – man verhindert aber das zwei verschiedene Threads auf die Datei zugreifen.

Eine tolle Session zum Thema “Async” gab es auch auf der Build 2013 – auch dort wurde diese Methode angewandt, daher gehe ich mal davon aus, dass dies “in Ordnung” ist.

Ergebnis

Mit diesen Mitteln kann man recht einfach und “stabil" (aktuell konnte ich noch kein Problem feststellen ;)) seine Daten in eine JSON-Datei schreiben und wieder auslesen. Der Code sollte auch für das “normale” WinRT lauffähig sein.

Ich habe selbst immer einen grossen Respekt vor Datei-Operationen und das Async ist mir leider noch nicht so vertraut – daher: Wer Verbesserungsvorschläge hat – immer her damit :)

Download auf GitHub

Das gesamte Projekt gibt es auf GitHub.


Written by Robert Muehsig

Software Developer - from Saxony, Germany - working on primedocs.io. Microsoft MVP & Web Geek.
Other Projects: KnowYourStack.com | ExpensiveMeeting | EinKofferVollerReisen.de