Как кэшировать изображения на клиенте для приложения WPF?

Мы разрабатываем настольное приложение WPF, которое отображает изображения, которые в настоящее время извлекаются через HTTP.

Изображения уже оптимизированы для качества/размера, но каждый раз, когда изображение извлекается, есть очевидное ожидание.

Есть ли способ кэшировать изображения на клиенте, чтобы они не загружались каждый раз?

Ответ 1

Для людей, приезжающих сюда через Google, я упаковал оригинальную реализацию, которую Саймон Хартчер опубликовал, реорганизован Jeroen van Langen (наряду с трюками от Иван Леоненко, чтобы сделать его связанным), в пакет NuGet с открытым исходным кодом.

Подробнее см. здесь http://floydpink.github.io/CachedImage/

Ответ 2

Я знаю, что этот вопрос очень старый, но в последнее время мне пришлось использовать кэширование в приложении WPF и обнаружил, что в .Net 3.5 есть намного лучший вариант с BitmapImage, установив UriCachePolicy, который будет использовать кэширование на системном уровне:

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

Вы даже можете установить значение в app.config, чтобы все ваше приложение использовало значение по умолчанию для кеширования:

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

Здесь вы найдете объяснение значений RequestCacheLevel: http://msdn.microsoft.com/en-us/library/system.net.cache.requestcachelevel(v = vs .110).aspx

Эта функциональность понимает заголовки HTTP/1.1, поэтому, если вы установите Revalidate, она использует заголовок If-Modified-Since, чтобы не загружать ее каждый раз, но все же проверяя, было ли изображение изменено, поэтому вы всегда имеете правильный.

Ответ 3

Я решил это, создав Binding Converter, используя IValueConverter интерфейс. Учитывая, что я пытался найти твердое решение для этого в течение по крайней мере недели, я решил, что должен поделиться своим решением для тех, у кого есть эта проблема в будущем.

Вот мой пост в блоге: Кэширование изображений для рабочего стола WPF

Ответ 4

Я прочитал ваш блог, и это привело меня к этой (я думаю, намного проще) концепции:

Как вы заметили, я повторно использовал часть вашего кода, которую вы поделили, поэтому я поделюсь с вами.

Создайте новый настраиваемый элемент управления 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;
            }
        }
    }
}

Далее я создал класс FileCache (поэтому у меня есть контроль над всеми кешированием не только изображений)

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

Также для загрузки содержимого я создал класс-помощник:

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

HttpHelper использует расширение класса WebResponse для чтения результата в массив

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

Теперь вы получили его, давайте его использовать в xaml

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

Это все, многократно используемое и надежное.

Единственный недостаток заключается в том, что изображение никогда не загружается повторно, пока вы не очистите каталог кеша.

Первый раз изображение загружается из Интернета и сохраняется в каталоге кеша. В конечном итоге изображение загружается из кеша и назначается источнику родительского класса (Image).

С уважением, Йерун ван Ланген.

Ответ 5

Если вы просто пытаетесь кэшировать в рамках одного и того же запуска, то локальный словарь может работать как кеш времени выполнения.

Если вы пытаетесь кэшировать между запусками приложений, это становится более сложным.

Если это настольное приложение, просто сохраните кэшированные изображения локально в папке данных пользовательских приложений.

Если это приложение XBAP (WPF в браузере), вы сможете настроить локальный кэш в изолированное хранилище, из-за безопасности.

Ответ 6

Основываясь на этом, я сделал собственный контроль, который:

  • может загружать изображения асинхронно и получать их из кеша, если изображение
  • является потокобезопасным
  • было загружено свойство зависимостей, к которому вы можете привязываться к
  • обновлять изображения, предоставляя новые имена в исходном фиде (не забудьте сохранить чистую операцию кэширования, например, вы можете анализировать фид и асинхронно удалять изображения без ссылок в фиде)

Я сделал сообщение в блоге:, и вот код:

public class CachedImage : Image
{
    static CachedImage()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(CachedImage), new FrameworkPropertyMetadata(typeof(CachedImage)));
    }

    public readonly static DependencyProperty ImageUrlProperty = DependencyProperty.Register("ImageUrl", typeof(string), typeof(CachedImage), new PropertyMetadata("", ImageUrlPropertyChanged));

    public string ImageUrl
    {
        get
        {
            return (string)GetValue(ImageUrlProperty);
        }
        set
        {
            SetValue(ImageUrlProperty, value);
        }
    }

    private static readonly object SafeCopy = new object();

    private static void ImageUrlPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var url = (String)e.NewValue;
        if (String.IsNullOrEmpty(url))
            return;

        var uri = new Uri(url);
        var localFile = String.Format(Path.Combine(Globals.CacheFolder, uri.Segments[uri.Segments.Length - 1]));
        var tempFile = String.Format(Path.Combine(Globals.CacheFolder, Guid.NewGuid().ToString()));

        if (File.Exists(localFile))
        {
            SetSource((CachedImage)obj, localFile);
        }
        else
        {
            var webClient = new WebClient();
            webClient.DownloadFileCompleted += (sender, args) =>
                                                    {
                                                        if (args.Error != null)
                                                        {
                                                            File.Delete(tempFile);
                                                            return;
                                                        }
                                                        if (File.Exists(localFile))
                                                            return;
                                                        lock (SafeCopy)
                                                        {
                                                            File.Move(tempFile, localFile);
                                                        }
                                                        SetSource((CachedImage)obj, localFile);
                                                    };

            webClient.DownloadFileAsync(uri, tempFile);
        }
    }

    private static void SetSource(Image inst, String path)
    {
        inst.Source = new BitmapImage(new Uri(path));
    }
}

Теперь вы можете привязать к нему:

<Cache:CachedImage ImageUrl="{Binding Icon}"/>

Ответ 7

Просто обновление от Jeroen van Langen,

Вы можете сохранить кучу строки

удалите класс HttpHelper и WebResponse_extension

заменить

HttpHelper.GetAndSaveToFile(url, localFile);

по

WebClient webClient = new WebClient();
    webClient.DownloadFile(url, localFile);