BitmapImage has a UriCachePolicy property. You can simply set it to RequestCacheLevel.Default to get an IE-like cache policy with all exceptions handled properly.
private static void ImageUrlPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var url = e.NewValue as string;
if (string.IsNullOrEmpty(url))
return;
var cachedImage = (Image) obj;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.UriSource = new Uri(url);
bitmapImage.UriCachePolicy = new RequestCachePolicy(RequestCacheLevel.Default);
bitmapImage.EndInit();
cachedImage.Source = bitmapImage;
}
There are also some issues in your code:
WebRequest exception is not handled properly. If an request responses 404, the application will crash.
Not thread-safety. If I have some CachedImage in the same view, and request the same url simultaneously, it will happen write-write confliction on the same file.
Memory wasting. You read all bytes from web response to memory then write to file. But you pass the file uri to BitmapImage, it will read all bytes from the file to memory again. Instead, it's better to directly construct a MemoryStream from the web response and pass it to BitmapImage.StreamSource.
I tried to rewrite FileCache.cs before I discovered the UriCachePolicy property of BitmapImage. This is what I have done and it worked correctly in my scenario where hundreds of CachedImage control request some random image simultaneously.
// A private field to record if a file is being written.
private static readonly Dictionary<string, bool> IsWritingFile = new Dictionary<string, bool>();
public static async Task<MemoryStream> HitAsync(string url)
{
if (!Directory.Exists(AppCacheDirectory))
{
Directory.CreateDirectory(AppCacheDirectory);
}
var uri = new Uri(url);
var fileNameBuilder = new StringBuilder();
using (var sha1 = new SHA1Managed())
{
var canonicalUrl = uri.ToString();
var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes(canonicalUrl));
fileNameBuilder.Append(BitConverter.ToString(hash).Replace("-", "").ToLower());
if (Path.HasExtension(canonicalUrl))
fileNameBuilder.Append(Path.GetExtension(canonicalUrl));
}
var fileName = fileNameBuilder.ToString();
var localFile = string.Format("{0}\\{1}", AppCacheDirectory, fileName);
var memoryStream = new MemoryStream();
FileStream fileStream = null;
if (!IsWritingFile.ContainsKey(fileName) && File.Exists(localFile))
{
using (fileStream = new FileStream(localFile, FileMode.Open, FileAccess.Read))
{
await fileStream.CopyToAsync(memoryStream);
}
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
var request = WebRequest.Create(uri);
request.Timeout = 30;
try
{
var response = await request.GetResponseAsync();
var responseStream = response.GetResponseStream();
if (responseStream == null)
return null;
if (!IsWritingFile.ContainsKey(fileName))
{
IsWritingFile[fileName] = true;
fileStream = new FileStream(localFile, FileMode.Create, FileAccess.Write);
}
using (responseStream)
{
var bytebuffer = new byte[100];
int bytesRead;
do
{
bytesRead = await responseStream.ReadAsync(bytebuffer, 0, 100);
if (fileStream != null)
await fileStream.WriteAsync(bytebuffer, 0, bytesRead);
await memoryStream.WriteAsync(bytebuffer, 0, bytesRead);
} while (bytesRead > 0);
if (fileStream != null)
{
await fileStream.FlushAsync();
fileStream.Dispose();
IsWritingFile.Remove(fileName);
}
}
memoryStream.Seek(0, SeekOrigin.Begin);
return memoryStream;
}
catch (WebException)
{
return null;
}
}
In Image.cs:
private static async void ImageUrlPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var url = e.NewValue as string;
if (string.IsNullOrEmpty(url))
return;
var memoryStream = await FileCache.HitAsync(url);
if (memoryStream == null)
return;
try
{
var cachedImage = (Image) obj;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memoryStream;
bitmapImage.EndInit();
cachedImage.Source = bitmapImage;
}
catch (Exception)
{
// ignored, in case the downloaded file is a broken or not an image.
}
}
BitmapImage has a UriCachePolicy property. You can simply set it to RequestCacheLevel.Default to get an IE-like cache policy with all exceptions handled properly.
There are also some issues in your code:
I tried to rewrite FileCache.cs before I discovered the UriCachePolicy property of BitmapImage. This is what I have done and it worked correctly in my scenario where hundreds of CachedImage control request some random image simultaneously.
In Image.cs: