Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

[WP7] Besoin d’avoir des données en cache

Les développeurs ASP.NET ont l’habitude de mettre des données en cache pour éviter de requêter a chaque fois la base de données. Et il est toujours utilie de penser que vos utilisateurs mobiles n’ont pas troujours une super connexion 3G/WIFI et un forfait ilimité. Il est donc utile de mettre des données en cache, lors d’un développement WP7, le reflexe à avoir pour ajouter de la persitence à des données est l’isolatedstorage.

Si comme en ASP.NET, vous souhaitez ajouter une expiration, c’est a dire que l’objet n’est disponible que pendant une certaine durée, vous devez passer par du code spécifique, il est n’est pas possible de le faire en standard.

Pour réaliser cette fonctionnalité, il faut développer un wrapper, donc voici les étapes de code:

La première etape est de créer une classe Cache qui contient un singleton, il contient également les valeurs pour NoAbsoluteExpiration et NoSlidingExpiration.

    public class Cache
    {
        public static readonly DateTime NoAbsoluteExpiration = DateTime.MaxValue;
        public static readonly TimeSpan NoSlidingExpiration = TimeSpan.Zero;

// Accès à l'isolatedstorage
        readonly IsolatedStorageFile _myStore = IsolatedStorageFile.GetUserStoreForApplication();

// singleton
        private static Cache _current;
        public static Cache Current
        {
            get { return _current ?? (_current = new Cache()); }
        }

Comme l’api ASP.NET le propose, nous ajoutons une méthode pour ajouter un objet au cache et définir les expirations.

Un objet en cache est défini par une clef (pour le retrouver),  l’objet en lui même qui sera sérialisé et les éléments d’expiration.

/// <summary>
/// Adds the specified key.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
/// <param name="absoluteExpiration">The absolute expiration.</param>
/// <param name="slidingExpiration">The sliding expiration.</param>
public void Add(string key, object value, DateTime absoluteExpiration, TimeSpan slidingExpiration)
{
    lock (_sync)
    {
        if (Contains(key))
            Remove(key);

        if (absoluteExpiration == NoAbsoluteExpiration)
            Add(key, DateTime.UtcNow + slidingExpiration, value);
        if (slidingExpiration == NoSlidingExpiration)
            Add(key, absoluteExpiration, value);
    }
}

Puis nous ajoutons concrètement l’objet dans l’isolated storage.

Pour chaque key, un répertoire avec son nom est créé, puis dans ce répertoire est créé un fichier nommer avec la date d’expiration au format Windows file time UTC (Une heure de fichier Windows est une valeur 64 bits qui représente le nombre d'intervalles de 100 nanosecondes qui se sont écoulés depuis 12:00 minuit, le 1er janvier 1601 après Jésus-Christ(notre ère) en temps universel coordonné (UTC)).

 

/// <summary>
/// Adds the specified key.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="expirationDate">The expiration date.</param>
/// <param name="value">The value.</param>
private void Add(string key, DateTime expirationDate, object value)
{
    lock (_sync)
    {
        if (!_myStore.DirectoryExists(key))
            _myStore.CreateDirectory(key);
        else
        {
	// Si la key existe déja, on supprime le répertoire et le fichier
            string currentFile = _myStore.GetFileNames(string.Format("{0}\\*.cache", key).FirstOrDefault();
            if (currentFile != null)
                _myStore.DeleteFile(string.Format("{0}\\{1}", key, currentFile));
            _myStore.DeleteDirectory(key);
            _myStore.CreateDirectory(key);
        }

        string fileName = string.Format("{0}\\{1}.cache", key, expirationDate.ToFileTimeUtc());


        if (_myStore.FileExists(fileName))
            _myStore.DeleteFile(fileName);

      using (var isolatedStorageFileStream = new IsolatedStorageFileStream(fileName, FileMode.OpenOrCreate, _myStore))
         {
           DataContractSerializer s = new DataContractSerializer(value.GetType());
           s.WriteObject(isolatedStorageFileStream, value);
         }
    }
}

Puis la dernière étape est de recuperer la valeur dans le cache, si le cache a expiré, la méthode retourne null.

/// <summary>
/// Gets the specified key.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">The key.</param>
/// <returns></returns>
public T Get<T>(string key)
{
    lock (_sync)
    {
// Récuperation du fichier
        string currentFile =  _myStore.GetFileNames(string.Format("{0}\\*.cache", key).FirstOrDefault();
        if (currentFile != null)
        {
// verification si le cache n'a pas expiré
            var expirationDate =
                DateTime.FromFileTimeUtc(long.Parse(Path.GetFileNameWithoutExtension(currentFile)));
            if (expirationDate >= DateTime.UtcNow)
            {
// Deserialisation de l'objet
                using (var isolatedStorageFileStream = new IsolatedStorageFileStream(
string.Format(@"{0}\{1}", key, currentFile), FileMode.Open, _myStore))
                {
                    DataContractSerializer s = new DataContractSerializer(typeof(T));
                    var value = s.ReadObject(isolatedStorageFileStream);
                    isolatedStorageFileStream.Close();
                    return (T)value;
                }
            }
// Si la date d'expiration est dépassé, on supprime le fichier de l'isolatedstorage
            Remove(key);
        }
        return default(T);
    }
}

Maintenant que nous avons les bases d’un sytème de cache, voici comment nous pouvons l’utiliser facilement et en y mettans un peut de Rx dans tout ca. Dans cette exemple, nous avons une methode GetAll qui renvoie des données depuis un service WCF. Le MyServiceClient a été généré par Visual Studio.

public void GetAll(Action<ObservableCollection<MyEntity>> callback)
{
    // Verification si l'objet est en cache et n'a pas expiré
    if (Cache.Current.Contains("KEY1"))
    {
        // Lecture du cache dans un nouveau thread, et appel du callback dans le dispatcher thread
        Observable.Start(() =>
                            Cache.Current.Get<ObservableCollection<MyEntity>>("KEY1"), Scheduler.ThreadPool).
            ObserveOn(Scheduler.Dispatcher).Subscribe(callback);
        return;
    }
    // Création du client pour le service WCF
    MyServiceClient client = new MyServiceClient();
    Observable.FromEvent<GetAllCompletedEventArgs>(client, "GetAllCompleted")
            .ObserveOn(Scheduler.ThreadPool)
            .Select(s =>
            {
                // Dans un nouveau thread, on ajoute l'objet recu par WCF dans le cache,
                	//sa durée de vie est de 1 jour
                if (s.EventArgs.Error == null)
                {
                    Cache.Current.Add("KEY1", s.EventArgs.Result, Cache.NoAbsoluteExpiration, TimeSpan.FromDays(1));
                }
                return s;
            })
        .ObserveOn(Scheduler.Dispatcher).Subscribe(s =>
        {
            // Puis dans le dispatcher thread, on appel le callback
            callback(s.EventArgs.Result);
        });
    // Appel de la méthode WCF
    client.GetAllAsync();
}

Si vous n’avez que des “settings” à sauvegarder, je vous conseil d’utiliser les API standard:

IsolatedStorageSettings.ApplicationSettings.Add("key", value);
IsolatedStorageSettings.ApplicationSettings.Save();

Ce code est utilisé pour le projet Warnygo, et est bien sur disponible au complet: Phone7.Fx.Preview.zip

Si vous avez des retours, n’hésitez pas !

Publié mercredi 1 septembre 2010 15:12 par Nicolas
Classé sous : ,
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires

# re: [WP7] Besoin d’avoir des données en cache

Intéressant ! Merci beaucoup (^__^)

mercredi 1 septembre 2010 19:06 by Danuz
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- [SharePoint] Les sessions TechDays 2012… par Le blog de Patrick [MVP SharePoint] le il y a 4 heures et 27 minutes

- TechDays Paris 2012 : Session pleinière jour 3 par Blog Technique de Romelard Fabrice le 02-09-2012, 11:01

- Mishra Reader : un lecteur RSS très Zune Style en Open Source ! par Cyril Sansus le 02-09-2012, 08:28

- [framework 4] Les Tasks et le Thread UI par Fathi Bellahcene le 02-09-2012, 00:33

- Workflow Foundation 3 a un pied dans la tombe par Blog de Jérémy Jeanson le 02-08-2012, 22:15

- TechDays Paris 2012 : Nouvelles tendances du poste de travail - Bring Your own PC par Blog Technique de Romelard Fabrice le 02-08-2012, 19:42

- TechDays Paris 2012 : System Center Service Manager 2012 Vue d’ensemble par Blog Technique de Romelard Fabrice le 02-08-2012, 17:32

- TechDays Paris 2012 : Pleinière second jour par Blog Technique de Romelard Fabrice le 02-08-2012, 16:23

- TechDays Paris 2012 : Retour d'expérience sur la mise en place d'un Cloud Privé par Blog Technique de Romelard Fabrice le 02-08-2012, 16:04

- TechDays Paris 2012 : Comment SharePoint a sauvé mes TechDays par Blog Technique de Romelard Fabrice le 02-07-2012, 23:59