Motivation

Storing and loading data for Windows Store, Windows Phone and universal Windows, apps requires some code, especially if you want to use the performant DataContract serializers. Enough.Storage comes to the rescue!

Usage

You need model classes that implement the DataContract attribute, for example:

using System.Runtime.Serialization;

namespace MyModel
{
  [DataContract]
  public class MyData 
  {
    [DataMember]
    public int Counter{ get; set; }
   
    [DataMember]
    public string Name { get; set; }
   
    [IgnoreDataMember]
    public string NameWithCounter { get { return Name + " (" + Counter + ")"; } }
  }
}

You can now save, load and delete stored objects using Enough.Storage.StorageHelper:

Save Objects

To save an object use the StorageHelper.SaveObjectAsync() method:

using Enough.Storage;

namespace MyViewModel
{
  public class MyDataViewModel
  {
     private MyData _myData;

     public async void Save()
     {
       await StorageHelper.SaveObjectAsync(_myData);
     }
  }
}

You can optionally define a filename, a storage location/folder a,serializer type and a list of known types:

await StorageHelper.saveObjectAsync(_myData, fileName: "myfilename", storageLocation: StorageLocation.Roaming, serializerType: SerializerType.DataContractJson, knownTypes: new Type[]{ Car, Bike});

Choose on of the following StorageLocations:
  • StorageLocation.Local: by default StorageHelper saves files in the local filesystem.
  • StorageLocation.Roaming: use the roaming folder to make the data available on all Windows powered handsets of your user.
  • StorageLocation.Temporary: use the temporary folder to store data such as the session state which is not needed after your app exits
Instead of a standard storage location you can also specify any StorageFolder directly as well, for example:

await StorageHelper.saveObjectAsync(_myData, fileName: "myfilename", folder: ApplicationData.Current.LocalFolder, serializerType: SerializerType.DataContractJson, knownTypes: new Type[]{ Car, Bike});

The folder parameter overrides the StorageLocation parameter.

You can use any of the following SerializerTypes:
  • SerializerType.DataContractJson: by default the DataContractJsonSerializer is being used.
  • SerializerType.DataContractXml: you can also choose the XML based DataContractSerializer if you like

Load Objects

Load objects using either StorageHelper.LoadObjectAsync<T>() or StorageHelper.TryLoadObjectAsync<T>():

_myData = await StorageHelper.TryLoadObjectAsync<MyData>();

TryLoadObjectAsync() tries to load the object and returns null when not successful, while LoadObjectAsync() will throw an exception when not successful.
As with saving objects you can optionally define a fileName, a storage location/folder, a serializer type and the known types:

_myData = await StorageHelper.TryLoadObjectAsync<MyData>(fileName: "myfilename", storageLocation: StorageLocation.Roaming, serializerType: SerializerType.DataContractJson, knownTypes: new Type[]{ Car, Bike});
or with a direct storage folder:

_myData = await StorageHelper.TryLoadObjectAsync<MyData>(fileName: "myfilename", folder: ApplicationData.Current.LocalFolder, serializerType: SerializerType.DataContractJson, knownTypes: new Type[]{ Car, Bike});

Deleting Stored Objects

Delete objects with StorageHelper.DeleteObjectAsync<T>():

await StorageHelper.DeleteObjectAsync<MyData>();

And again you can provide the following optional parameters:

await StorageHelper.DeleteObjectAsync<MyData>(fileName: "myfilename", storageLocation: StorageLocation.Roaming, serializerType: SerializerType.DataContractJson);
or with specifying a storage folder directly:

await StorageHelper.DeleteObjectAsync<MyData>(fileName: "myfilename", folder: ApplicationData.Current.LocalFolder, serializerType: SerializerType.DataContractJson);

Specifying the Known Types

When you want to store lists that contain interface types, you need to specify the used types. You can do that by specifying the knownTypes parameter:
List<IVehicle>> myVehicles = await StorageHelper.TryLoadObjectAsync<List<IVehicle>>(fileName: "myfilename", knownTypes: new Type[]{ Car, Bike});

You can specify the knownTypes parameter in LoadObjectAsync, SaveObjectAsync and TryLoadObjectAsync.

Working with Stream Data

StorageHelper also allows you to store stream data like downloaded images with a single line of code. Use the following methods for this:

Storing a Stream

await StorageHelper.SaveStreamAsync(stream, fileName: "cachedimage");

You can optionally specify either a StorageLocation or a StorageFolder for placing the file.
Specifying a folder:
await StorageHelper.SaveStreamAsync(stream, fileName, folder: ApplicationData.Current.LocalFolder);
Specifying a storage location:
await StorageHelper.SaveStreamAsync(stream, fileName, storageLocation: StorageLocation.Roaming);

Opening a Stream

try
{
   Stream stream = await StorageHelper.OpenStreamAsync(fileName: "cachedimage");
}
catch (Exception ex)
{
   // handle error
}

Or, in case you don't want to handle any exceptions, use TryOpenStreamAsync instead:
Stream stream = await StorageHelper.TryOpenStreamAsync(fileName: "cachedimage");
if (stream != null)
{
   //  process data
}

As with SaveStreamAsync you can optionally specify either a folder or a storageLocation.

Deleting a Stream File

bool success = await StorageHelper.DeleteStreamAsync(fileName: "cachedimage");

As with SaveStreamAsync you can optionally specify either a folder or a storageLocation.

Checking if a File Exists

bool exists = await StorageHelper.ExistsAsync(fileName: "cachedimage");

As with SaveStreamAsync you can optionally specify either a folder or a storageLocation.

Installation

Install the Enough.Storage NuGet package in the Package Management Console:

PM> Install-Package Enough.Storage 

Alternatively right-click your project references, select "Manage NuGet Packages..." and search for "Enough.Storage".

Synchronizing File Access

When saving and loading data asynchronously you might end up saving or loading the same data at the same time. In this case you will get file access exceptions. It might be wise to lock access to the relevant code blocks. You can use the Enough.AsyncLock package for doing so:

using Enough.Storage;
using Enough.Async;

namespace MyApp
{
  public class MyDataRetriever
  {
     private AsyncLock _asyncLock = new AsyncLock();

     public async Task<MyData> GetData()
     {
        using (await _asyncLock.LockAsync())
        {
            MyData data = await StorageHelper.TryLoadObjectAsync<MyData>();
            if (data == null)
            {
               data = await GetDataFromWebAsync();
               await StorageHelper.SaveObjectAsync(data);
            }
        }
        return data;
     }
   }
}







Last edited May 20, 2014 at 8:46 AM by enoughrob, version 13