[WP7] Do you need to cache data ?

ASP.NET developers  implement caching data frequently to avoid requesting  the database each time. It is always useful to think that your mobile users don’t have a super 3G/WIFI connection every time.

If as in ASP.NET, you want to add an expiration, meaning that the object is available for a certain period, you must pass by specific code, it’s not possible with standard API.

To achieve this functionality, you must develop a wrapper, so let’s take a look on the code:

The first step is to create a class named cache that contains a singleton, it also contains values for NoAbsoluteExpiration and NoSlidingExpiration.

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

// Acces to the isolatedstorage
        readonly IsolatedStorageFile _myStore = IsolatedStorageFile.GetUserStoreForApplication();

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

Like the ASP.NET API, we write a method to add an item to the cache and to set the timeouts.

An object in the cache is defined by a key (to return it), the object that will be serialized and the timespan/datetime parameters.

/// <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);
    }
}

Then we add the object in the isolated storage. For each key, a directory with the key as name is created, then we create a file named with the expiration date with the Windows file time UTC format (A Windows file time is a 64-bit value that represents the number of intervals of 100 nanoseconds that have elapsed since 12: 00 midnight, 1 January 1601).

/// <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
        {
    // If the key exist, we delete the directory and the file
            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);
         }
    }
}

Then the last step is to retrieve the value in the cache, if the cache has expired, the method returns 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)
    {
// Getting th file
        string currentFile =  _myStore.GetFileNames(string.Format("{0}\\*.cache", key).FirstOrDefault();
        if (currentFile != null)
        {
// We check if the cache has not expired
            var expirationDate =
                DateTime.FromFileTimeUtc(long.Parse(Path.GetFileNameWithoutExtension(currentFile)));
            if (expirationDate >= DateTime.UtcNow)
            {
// read the file from the isolatedstorage
                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;
                }
            }
// If the cache has expired, we delete the file from the isolatedstorage
            Remove(key);
        }
        return default(T);
    }
}

Now that we have the foundations for a cache system,  we can use it easily with a bit of Rx. In this example, we have a GetAll method that returns data from a WCF service. The MyServiceClient was generated by Visual Studio.

public void GetAll(Action<ObservableCollection<MyEntity>> callback)
{
    // We check if the cache has not expired
    if (Cache.Current.Contains("KEY1"))
    {
        // W read the cache in a new thread then we call the callback in the dispatcher thread
        Observable.Start(() =>
                            Cache.Current.Get<ObservableCollection<MyEntity>>("KEY1"), Scheduler.ThreadPool).
            ObserveOn(Scheduler.Dispatcher).Subscribe(callback);
        return;
    }
    // New WCF client
    MyServiceClient client = new MyServiceClient();
    Observable.FromEvent<GetAllCompletedEventArgs>(client, "GetAllCompleted")
            .ObserveOn(Scheduler.ThreadPool)
            .Select(s =>
            {
                // In a new thread, we had the object received by WCF in the cache,
                    //the life time is 1 day
                if (s.EventArgs.Error == null)
                {
                    Cache.Current.Add("KEY1", s.EventArgs.Result, Cache.NoAbsoluteExpiration, TimeSpan.FromDays(1));
                }
                return s;
            })
        .ObserveOn(Scheduler.Dispatcher).Subscribe(s =>
        {
            // Then in the dispacher thread, we call the callback
            callback(s.EventArgs.Result);
        });
    // Async call for the GetAll method
    client.GetAllAsync();
}

This code is used for the Warnygo project, and is of course available in full: Phone7.Fx.Preview.zip

If you have feedback, you are welcome !

Pingbacks and trackbacks (9)+

Comments are closed

Hi there !

 

Specialized in .net technologies for many years, I am a technology fan in both asp.net and wpf/silverlight, using c# and .net 4.5

Taking advantages of new opportunities offered by the Windows Azure platform and WP/Win 8, I develop applications for Windows Phone, 5 of them are already available on the market place.


 

 

Month List