Thursday, August 1, 2013

WinRT IsoStorage Sample




First we should create a Storage Helper:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Serialization;
using Windows.Storage;
using System.IO;
using Windows.Storage.Streams;
using System.Xml.Linq;
using IsoStorageSample.Helpers;

namespace IsoStorageSample.Helpers
{
    public enum StorageType { Local, Temporary, Roaming }

    public class IsoStorage<T>
    {
        protected ApplicationData appData = Windows.Storage.ApplicationData.Current;
        protected StorageFolder storageFolder;
        protected StorageType storageType;

        public StorageType StorageType
        {
            get { return storageType; }
            set
            {
                storageType = value;
                // set the storage folder
                switch (storageType)
                {
                    case StorageType.Local:
                        storageFolder = appData.LocalFolder;
                        break;
                    case StorageType.Temporary:
                        storageFolder = appData.TemporaryFolder;
                        break;
                    case StorageType.Roaming:
                        storageFolder = appData.RoamingFolder;
                        break;
                    default:
                        throw new Exception(String.Format("Unknown StorageType: {0}", storageType));
                }
            }
        }

        public IsoStorage() : this(StorageType.Local) { }
        public IsoStorage(StorageType type)
        {
            StorageType = type;
        }

        public async Task CopyAsync(StorageFolder folder, string fileName)
        {
            try
            {
                StorageFile storageFile = await folder.GetFileAsync(fileName);

                if (await GetFileIfExistsAsync(storageFile.Name) == null)
                {
                    await storageFile.CopyAsync(storageFolder);
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        public async Task<XDocument> LoadXml(string fileName)
        {
            try
            {
                StorageFile xmlFile = await GetFileIfExistsAsync(fileName);

                if (xmlFile != null)
                {
                    return XDocument.Load(xmlFile.Path);
                }
                else
                {
                    return null;
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Delete a file asynchronously
        /// </summary>
        /// <param name="fileName"></param>
        public async void DeleteAsync(string fileName)
        {
            try
            {
                fileName = AppendExt(fileName);
                var file = await GetFileIfExistsAsync(fileName);
                if (file != null)
                    await file.DeleteAsync();
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// At the moment the only way to check if a file exists to catch an exception... :/
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        protected async Task<StorageFile> GetFileIfExistsAsync(string fileName)
        {
            try
            {
                return await storageFolder.GetFileAsync(fileName);
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// Appends the file extension to the given filename
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        protected string AppendExt(string fileName)
        {
            if (fileName.Contains(".txt"))
                return fileName;
            else
                return string.Format("{0}.txt", fileName);
        }

        /// <summary>
        /// Saves a serialized object to storage asynchronously
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="obj"></param>
        public async void SaveAsync(string fileName, T data, string key = null)
        {
            try
            {
                if (data == null)
                    return;

                fileName = AppendExt(fileName);
                var file = await storageFolder.CreateFileAsync(fileName, CreationCollisionOption.ReplaceExisting);

                string serializedObject = JSONSerializerHelper.Serialize(data);
                string encryptedObject = serializedObject;

                if (key != null)
                {
                    encryptedObject = Hashing.Encrypt(serializedObject, key);
                }

                await FileIO.WriteTextAsync(file, encryptedObject);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// Load a given filename asynchronously
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public async Task<T> LoadAsync(string fileName, string key = null)
        {
            try
            {
                fileName = AppendExt(fileName);
                StorageFile file = null;
                file = await storageFolder.GetFileAsync(fileName);

                string serializedObject = await FileIO.ReadTextAsync(file);
                string decryptedObject = serializedObject;

                if (key != null)
                {
                    decryptedObject = Hashing.Decrypt(serializedObject, key);
                }

                return (T)JSONSerializerHelper.DeSerialize(typeof(T), decryptedObject);
            }
            catch (FileNotFoundException)
            {
                //file not existing is perfectly valid so simply return the default
                return default(T);
                //throw;
            }
            catch (Exception)
            {
                //Unable to load contents of file
                throw;
            }
        }
    }
}


For serializing data, I use Json.NET and create a helper for that:

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace IsoStorageSample.Helpers
{
    public static class JSONSerializerHelper
    {
        public static string Serialize(object obj)
        {
            string json = JsonConvert.SerializeObject(obj, Formatting.Indented, new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects, TypeNameHandling = TypeNameHandling.Objects });
            return json;
        }

        public static object DeSerialize(Type type, string str)
        {
            object json = JsonConvert.DeserializeObject(str, type, new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects, TypeNameHandling = TypeNameHandling.Objects });
            return json;
        }
    }
}


And for hashing data I use Windows.Security.Cryptography

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;
using Windows.Storage.Streams;

namespace IsoStorageSample.Helpers
{
    public class Hashing
    {
        public static string Encrypt(string dataToEncrypt, string myKey)
        {
            //make sure we have data to work with
            if (string.IsNullOrEmpty(dataToEncrypt))
                throw new ArgumentException("input cannot be empty");

            if (string.IsNullOrEmpty(myKey))
                throw new ArgumentException("password cannot be empty");

            // get IV, key and encrypt
            var iv = CreateInitializationVector(myKey);
            var key = CreateKey(myKey);

            var encryptedBuffer = CryptographicEngine.Encrypt(key, CryptographicBuffer.ConvertStringToBinary(dataToEncrypt, BinaryStringEncoding.Utf8), iv);
            return CryptographicBuffer.EncodeToBase64String(encryptedBuffer);
        }

        public static string Decrypt(string encryptedString, string myKey)
        {
            try
            {
                //make sure we have data to work with
                if (string.IsNullOrEmpty(encryptedString))
                    throw new ArgumentException("input cannot be empty");

                if (string.IsNullOrEmpty(myKey))
                    throw new ArgumentException("password cannot be empty");

                // get IV, key and decrypt
                var iv = CreateInitializationVector(myKey);
                var key = CreateKey(myKey);

                var decryptedBuffer = CryptographicEngine.Decrypt(key, CryptographicBuffer.DecodeFromBase64String(encryptedString), iv);

                return CryptographicBuffer.ConvertBinaryToString(BinaryStringEncoding.Utf8, decryptedBuffer);
            }
            catch (Exception)
            {
                throw;
            }
        }

        private static IBuffer CreateInitializationVector(string password)
        {
            var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm("AES_CBC_PKCS7");
            var newPassword = password;

            // make sure we satify minimum length requirements
            while (newPassword.Length < provider.BlockLength)
            {
                newPassword = newPassword + password;
            }

            //create vecotr
            var iv = CryptographicBuffer.CreateFromByteArray(UTF8Encoding.UTF8.GetBytes(newPassword));
            return iv;
        }

        private static CryptographicKey CreateKey(string password)
        {
            var provider = SymmetricKeyAlgorithmProvider.OpenAlgorithm("AES_CBC_PKCS7");
            var newPassword = password;

            // make sure we satify minimum length requirements
            while (newPassword.Length < provider.BlockLength)
            {
                newPassword = newPassword + password;
            }

            var buffer = CryptographicBuffer.ConvertStringToBinary(newPassword, BinaryStringEncoding.Utf8);
            buffer.Length = provider.BlockLength;

            var key = provider.CreateSymmetricKey(buffer);
            return key;
        }
    }
}


Now we can use that like this:

private void SaveData()
{
    Dictionary<int, Person> persons = new Dictionary<int, Person>();

    persons.Add(1, new Person { ID = 1, Name = "Michael" });
    persons.Add(2, new Person { ID = 2, Name = "John" });
    persons.Add(3, new Person { ID = 3, Name = "Rose" });

    IsoStorage<Dictionary<int, Person>> storage = new IsoStorage<Dictionary<int, Person>>();

    //if key is not equal null => data will be encrypt
    storage.SaveAsync("TestStorage", persons, null);
}

private async void LoadData()
{
    IsoStorage<Dictionary<int, Person>> storage = new IsoStorage<Dictionary<int, Person>>();
    Dictionary<int, Person> persons = await storage.LoadAsync("TestStorage", null);
}

public class Person
{
    public int ID { get; set; }
    public string Name { get; set; }
}

Download full sample from here

No comments:

Post a Comment