Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I cache images on the client for a WPF application?

Tags:

http

image

wpf

We are developing a WPF desktop application that is displaying images that are currently being fetched over HTTP.

The images are already optimised for quality/size but there is an obvious wait each time that the image is fetched.

Is there a way to cache images on the client so that they aren't downloaded each time?

like image 949
Simon Hartcher Avatar asked Dec 10 '09 01:12

Simon Hartcher


3 Answers

I know this question is very old, but I had to use caching recently in a WPF application and found that there is a much better option in .Net 3.5 with BitmapImage by setting UriCachePolicy, that will use system-level caching:

<Image.Source>
  <BitmapImage UriCachePolicy="Revalidate" 
     UriSource="https://farm3.staticflickr.com/2345/2077570455_03891081db.jpg"/>
</Image.Source>

You can even set the value in the app.config to make all your app use a default value for caching:

<system.net>
  <requestCaching defaultPolicyLevel="CacheIfAvailable"/>
</system.net>

You will find an explanation of the RequestCacheLevel values here: http://msdn.microsoft.com/en-us/library/system.net.cache.requestcachelevel(v=vs.110).aspx

This functionality understands HTTP/1.1 headers, so if you set Revalidate it uses If-Modified-Since header to avoid downloading it each time, but still checking if the image has been changed so you always have the correct one.

like image 62
jmservera Avatar answered Nov 08 '22 20:11

jmservera


For people coming here via Google, I have packaged the original implementation that Simon Hartcher posted, refactored by Jeroen van Langen (along with the tweaks from Ivan Leonenko to make it bindable), into an Open Source NuGet package.

Please find the details here - http://floydpink.github.io/CachedImage/

like image 27
Hari Pachuveetil Avatar answered Nov 08 '22 22:11

Hari Pachuveetil


i've read your blog, and that brought me to this (i think much easier) concept setup:

As you will noticed, i reused some of your code you shared, so i'll share mine back.

Create a new custom control called CachedImage.

public class CachedImage : Image
{
    private string _imageUrl;

    static CachedImage()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(CachedImage), new FrameworkPropertyMetadata(typeof(CachedImage)));
    }

    public string ImageUrl
    {
        get
        {
            return _imageUrl;
        }
        set
        {
            if (value != _imageUrl)
            {
                Source = new BitmapImage(new Uri(FileCache.FromUrl(value)));
                _imageUrl = value;
            }
        }
    }
}

Next i've made a FileCache class (so i have control on all caching not only images)

public class FileCache
{
    public static string AppCacheDirectory { get; set; }

    static FileCache()
    {
        // default cache directory, can be changed in de app.xaml.
        AppCacheDirectory = String.Format("{0}/Cache/", Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData));
    }

    public static string FromUrl(string url)
    {
        //Check to see if the directory in AppData has been created 
        if (!Directory.Exists(AppCacheDirectory))
        {
            //Create it 
            Directory.CreateDirectory(AppCacheDirectory);
        }

        //Cast the string into a Uri so we can access the image name without regex 
        var uri = new Uri(url);
        var localFile = String.Format("{0}{1}", AppCacheDirectory, uri.Segments[uri.Segments.Length - 1]);

        if (!File.Exists(localFile))
        {
            HttpHelper.GetAndSaveToFile(url, localFile);
        }

        //The full path of the image on the local computer 
        return localFile;
    }
}

Also for downloading content I made a helper class:

public class HttpHelper
{
    public static byte[] Get(string url)
    {
        WebRequest request = HttpWebRequest.Create(url);
        WebResponse response = request.GetResponse();

        return response.ReadToEnd();
    }

    public static void GetAndSaveToFile(string url, string filename)
    {
        using (FileStream stream = new FileStream(filename, FileMode.Create, FileAccess.Write))
        {
            byte[] data = Get(url);
            stream.Write(data, 0, data.Length);
        }
    }
}

The HttpHelper uses an extension on the WebResponse class for reading the result to an array

public static class WebResponse_extension
{
    public static byte[] ReadToEnd(this WebResponse webresponse)
    {
        Stream responseStream = webresponse.GetResponseStream();

        using (MemoryStream memoryStream = new MemoryStream((int)webresponse.ContentLength))
        {
            responseStream.CopyTo(memoryStream);
            return memoryStream.ToArray();
        }
    }
}

Now you got it complete, lets use it in xaml

<Grid>
    <local:CachedImage ImageUrl="http://host/image.png" />
</Grid>

That's all, it's reusable and robust.

The only disadvance is, that the image is never downloaded again until you cleanup the cache directory.

The first time the image is downloaded from the web and saved in the cache directory. Eventually the image is loaded from the cache and assign to the source of the parent class (Image).

Kind regards, Jeroen van Langen.

like image 3
Jeroen van Langen Avatar answered Nov 08 '22 21:11

Jeroen van Langen