Silverlight 2 - Saving and Retrieving Images from IsolatedStorage
In one of my previous article, Display Images From Database In Silverlight 2, we explored how to pull images from the database using Silverlight. However, that approach was slow as the database was hit every time the user requested an Employee Image. In this article, we will see another approach where we will use the IsolatedStorage to store and retrieve images. Once the images have been fetched from the database and stored in the IsolatedStorage, all subsequent requests can then be routed to the IsolatedStorage, which makes responses super fast, as the images are now being fetched from the local disk of your machine.
If you are new to IsolatedStorage, read more about its advantages over here.
We will first create an Image Handler that will fetch images from the database for the first request. We will use the WebClient class to retrieve images using this handler. The images will then be stored in the IsolatedStorage.
We will make use of the Employees table in the Northwind database.
Step 1: Open Visual Studio 2008 > File > New Project > Select the language (C# or VB) > Select ‘Silverlight’ in the Project Types > from the templates, select ‘Silverlight Application’. Type a name ‘ImagesInIsolatedStorage’ and location for the project and click ok.
Note: If you are unable to view the templates, you do not have Microsoft Silverlight Tools for Visual Studio 2008. Check out this link to see how to obtain it.
Step 2: You will observe that a default page called ‘Page.xaml’ gets created. Create a few rows and columns and add a few controls like the Image, TextBlock and Button inside the Grid. The TextBlock will be used to display status messages whereas the Button will trigger an image fetch which will be displayed in the Image control. After setting a few layout properties, the markup of Page.Xaml will look similar to the following:
<UserControl x:Class="ImagesInIsolatedStorage.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.RowDefinitions>
<RowDefinition Height="200"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image x:Name="img1" Grid.Row="0" Grid.ColumnSpan="2" Visibility="Visible"></Image>
<TextBlock x:Name="txtStatus" Grid.Row="1" Grid.Column="0"/>
<Button x:Name="btnDisplayImg" Grid.Row="1" Grid.Column="1" Height="100" Width="100" Content="Display" Click="btnDisplayImg_Click"></Button>
</Grid>
</UserControl>
Step 3: Since we are retrieving the images from the database, we would need a connection string to the database. Add the following connection string to your web.config
<connectionStrings>
<add name="NorthwindConnectionString"
connectionString="Data Source=(local);Initial Catalog=Northwind;Integrated Security=True"
providerName="System.Data.SqlClient"/>
</connectionStrings>
Step 4: We will be using an HttpHandler to pull images from the database. Handlers provide a lot of flexibility while accessing server-side resources. To create an HttpHandler, right click project AccessImagesInDatabase.Web > Add New Item > Generic Handler > DisplayImage.ashx. Add the following code to the handler.
C#
using System;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Drawing.Imaging;
using System.Web;
using System.Web.Services;
namespace AccessImagesInDatabase.Web
{
///<summary>
/// Summary description for $codebehindclassname$
///</summary>
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class DisplayImage : IHttpHandler
{
byte[] empPic = null;
long seq = 0;
public void ProcessRequest(HttpContext context)
{
Int32 empno;
if (context.Request.QueryString["id"] != null)
empno = Convert.ToInt32(context.Request.QueryString["id"]);
else
throw new ArgumentException("No parameter specified");
// Convert Byte[] to Bitmap
Bitmap newBmp = ConvertToBitmap(ShowEmpImage(empno));
if (newBmp != null)
{
newBmp.Save(context.Response.OutputStream, ImageFormat.Jpeg);
newBmp.Dispose();
}
}
// Convert byte array to Bitmap (byte[] to Bitmap)
protected Bitmap ConvertToBitmap(byte[] bmp)
{
if (bmp != null)
{
TypeConverter tc = TypeDescriptor.GetConverter(typeof(Bitmap));
Bitmap b = (Bitmap)tc.ConvertFrom(bmp);
return b;
}
return null;
}
public byte[] ShowEmpImage(int empno)
{
string conn = ConfigurationManager.ConnectionStrings["NorthwindConnectionString"].ConnectionString;
SqlConnection connection = new SqlConnection(conn);
string sql = "SELECT photo FROM Employees WHERE EmployeeID = @ID";
SqlCommand cmd = new SqlCommand(sql, connection);
cmd.CommandType = CommandType.Text;
cmd.Parameters.AddWithValue("@ID", empno);
connection.Open();
SqlDataReader dr = cmd.ExecuteReader();
if (dr.Read())
{
seq = dr.GetBytes(0, 0, null, 0, int.MaxValue) - 1;
empPic = new byte[seq + 1];
dr.GetBytes(0, 0, empPic, 0, Convert.ToInt32(seq));
connection.Close();
}
return empPic;
}
public bool IsReusable
{
get
{
return false;
}
}
}
}
VB.NET
Imports System
Imports System.ComponentModel
Imports System.Configuration
Imports System.Data
Imports System.Data.SqlClient
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Web
Imports System.Web.Services
Namespace AccessImagesInDatabase.Web
''' <summary>
''' Summary description for $codebehindclassname$
''' </summary>
<WebService(Namespace := "http://tempuri.org/"), WebServiceBinding(ConformsTo := WsiProfiles.BasicProfile1_1)> _
Public Class DisplayImage
Implements IHttpHandler
Private empPic() As Byte = Nothing
Private seq As Long = 0
Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
Dim empno As Int32
If context.Request.QueryString("id") IsNot Nothing Then
empno = Convert.ToInt32(context.Request.QueryString("id"))
Else
Throw New ArgumentException("No parameter specified")
End If
' Convert Byte[] to Bitmap
Dim newBmp As Bitmap = ConvertToBitmap(ShowEmpImage(empno))
If newBmp IsNot Nothing Then
newBmp.Save(context.Response.OutputStream, ImageFormat.Jpeg)
newBmp.Dispose()
End If
End Sub
' Convert byte array to Bitmap (byte[] to Bitmap)
Protected Function ConvertToBitmap(ByVal bmp() As Byte) As Bitmap
If bmp IsNot Nothing Then
Dim tc As TypeConverter = TypeDescriptor.GetConverter(GetType(Bitmap))
Dim b As Bitmap = CType(tc.ConvertFrom(bmp), Bitmap)
Return b
End If
Return Nothing
End Function
Public Function ShowEmpImage(ByVal empno As Integer) As Byte()
Dim conn As String = ConfigurationManager.ConnectionStrings("NorthwindConnectionString").ConnectionString
Dim connection As New SqlConnection(conn)
Dim sql As String = "SELECT photo FROM Employees WHERE EmployeeID = @ID"
Dim cmd As New SqlCommand(sql, connection)
cmd.CommandType = CommandType.Text
cmd.Parameters.AddWithValue("@ID", empno)
connection.Open()
Dim dr As SqlDataReader = cmd.ExecuteReader()
If dr.Read() Then
seq = dr.GetBytes(0, 0, Nothing, 0, Integer.MaxValue) - 1
empPic = New Byte(seq){}
dr.GetBytes(0, 0, empPic, 0, Convert.ToInt32(seq))
connection.Close()
End If
Return empPic
End Function
Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return False
End Get
End Property
End Class
End Namespace
The steps for retrieving images from database, using the handler are as follows:
1. The EmployeeID whose image is to be retrieved is passed to the handler via query string. We use the Request.QueryString[“id”] to retrieve the EmployeeID(emp_id) from the handler url. The ID is then passed to the ‘ShowEmpImage()’ method where the image is fetched from the database using SqlDataReader and returned in a byte[] object.
2. We pass this byte[] to the ConvertToBitmap() function where we use the TypeConverter class to convert a byte array to bitmap.
3. The last step is to save the image to the page's output stream and indicate the image format as shown here convBmp.Save(context.Response.OutputStream, ImageFormat.Jpeg)
Note: Silverlight does not support .bmp images. For this reason, we set the ImageFormat to Jpeg while saving the image to the OutputStream.
Step 5: The next step is to use the WebClient class to fetch the image as shown below. Go back to Page.Xaml.cs or vb:
C#
WebClient wc;
int imgNo = 3;
public Page()
{
InitializeComponent();
}
private void btnDisplayImg_Click(object sender, RoutedEventArgs e)
{
IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication();
string imgnm = "Emp" + imgNo + ".jpg";
if (isoStore.FileExists(imgnm))
{
txtStatus.Text = "Image fetched from Isolated Storage";
DisplayImage(imgNo, imgnm);
}
else
{
string imgUri = "http://localhost:49858/DisplayImage.ashx?id=" + imgNo;
wc = new WebClient();
wc.OpenReadCompleted += new OpenReadCompletedEventHandler(wc_OpenReadCompleted);
wc.OpenReadAsync(new Uri(imgUri, UriKind.Absolute));
}
}
VB.NET
Private wc As WebClient
Private imgNo As Integer = 3
Private Sub btnDisplayImg_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim isoStore As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication()
Dim imgnm As String = "Emp" & imgNo & ".jpg"
If isoStore.FileExists(imgnm) Then
txtStatus.Text = "Image fetched from Isolated Storage"
DisplayImage(imgNo, imgnm)
Else
Dim imgUri As String = "http://localhost:49858/DisplayImage.ashx?id=" & imgNo
wc = New WebClient()
AddHandler wc.OpenReadCompleted, AddressOf wc_OpenReadCompleted
wc.OpenReadAsync(New Uri(imgUri, UriKind.Absolute))
End If
End Sub
In the btnDisplayImg_Click event, we first check if the image requested is in the IsolatedStorage. If the image exists, we display the image using DisplayImage() method. We will be seeing the DisplayImage() method in a moment. If the image does not exist, we initialize the WebClient object through a constructor, add the OpenReadCompleted event handler and then initiate the request to read the image as a stream using the OpenReadAsync(). The OpenReadAsyc accepts a URI which is the path to an ImageHandler in our case. The image is retrieved using the ImageHandler. We pass the EmployeeID (imgNo) as a query string parameter to the handler.
Note: If the protocol, path or the port number of the service differs from that of the Silverlight application, it is a Cross Domain call in Silverlight. You would need a clientaccesspolicy.xml kept in the root of the server where the service is hosted in order to make Cross Domain calls. Read more about it over here. In our example, we have kept both the port and path the same (http://localhost:49858/)
Step 6: Let us now add functionality to OpenReadCompleted() and the DisplayImage() method.
C#
void wc_OpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
{
if (e.Error == null)
{
try
{
IsolatedStorageFile isof = IsolatedStorageFile.GetUserStoreForApplication();
bool? chkRes = CheckAndGetMoreSpace(e.Result.Length);
if (chkRes == false)
{
throw new Exception("Cannot store image due to insufficient space");
}
string imgName = "Emp" + imgNo + ".jpg";
// Save the image to Isolated Storage
IsolatedStorageFileStream isfs = new IsolatedStorageFileStream(imgName, FileMode.Create, isof);
Int64 imgLen = (Int64)e.Result.Length;
byte[] b = new byte[imgLen];
e.Result.Read(b, 0, b.Length);
isfs.Write(b, 0, b.Length);
isfs.Flush();
isfs.Close();
isof.Dispose();
txtStatus.Text = "Image fetched from Database";
DisplayImage(imgNo, imgName);
}
catch (Exception ex)
{
txtStatus.Text = "Error while fetching image";
}
}
}
// Increase Isolated Storage Quota
protected bool CheckAndGetMoreSpace(Int64 spaceReq)
{
IsolatedStorageFile store = IsolatedStorageFile.GetUserStoreForApplication();
Int64 spaceAvail = store.AvailableFreeSpace;
if (spaceReq > spaceAvail)
{
if (!store.IncreaseQuotaTo(store.Quota + spaceReq))
{
return false;
}
return true;
}
return true;
}
protected void DisplayImage(int imgid, string imgnm)
{
using (IsolatedStorageFile isoStore = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream isoStream = isoStore.OpenFile(imgnm, FileMode.Open))
{
BitmapImage bmpImg = new BitmapImage();
bmpImg.SetSource(isoStream);
img1.Source = bmpImg;
}
}
}
VB.NET
Private Sub wc_OpenReadCompleted(ByVal sender As Object, ByVal e As OpenReadCompletedEventArgs)
If e.Error Is Nothing Then
Try
Dim isof As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication()
Dim chkRes As Nullable(Of Boolean) = CheckAndGetMoreSpace(e.Result.Length)
If chkRes.GetValueOrDefault() = False Then
Throw New Exception("Cannot store image due to insufficient space")
End If
Dim imgName As String = "Emp" & imgNo & ".jpg"
' Save the image to Isolated Storage
Dim isfs As New IsolatedStorageFileStream(imgName, FileMode.Create, isof)
Dim imgLen As Int64 = CLng(Fix(e.Result.Length))
Dim b(imgLen - 1) As Byte
e.Result.Read(b, 0, b.Length)
isfs.Write(b, 0, b.Length)
isfs.Flush()
isfs.Close()
isof.Dispose()
txtStatus.Text = "Image fetched from Database"
DisplayImage(imgNo, imgName)
Catch ex As Exception
txtStatus.Text = "Error while fetching image"
End Try
End If
End Sub
' Increase Isolated Storage Quota
Protected Function CheckAndGetMoreSpace(ByVal spaceReq As Int64) As Boolean
Dim store As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication()
Dim spaceAvail As Int64 = store.AvailableFreeSpace
If spaceReq > spaceAvail Then
If (Not store.IncreaseQuotaTo(store.Quota + spaceReq)) Then
Return False
End If
Return True
End If
Return True
End Function
Protected Sub DisplayImage(ByVal imgid As Integer, ByVal imgnm As String)
Using isoStore As IsolatedStorageFile = IsolatedStorageFile.GetUserStoreForApplication()
Using isoStream As IsolatedStorageFileStream = isoStore.OpenFile(imgnm, FileMode.Open)
Dim bmpImg As New BitmapImage()
bmpImg.SetSource(isoStream)
img1.Source = bmpImg
End Using
End Using
End Sub
The OpenReadCompleted contains a call to CheckAndGetMoreSpace() method. Now by default, there is 1MB of IsolatedStorage space available to Silverlight applications. The CheckAndGetMoreSpace() method calculates the extra space required to store the image and requests the user to allocate that space. We then save the image to the IsolatedStorage by getting a IsolatedStorageFileStream for each image and storing the data in a byte array. Finally the isfs.Write() is used to write a block of bytes to the IsolatedStorageFileStream.
Note: If you are wondering how to access the IsolatedStorage and view the image stored, then on my Vista box, the IsolatedStorage folder is over here:
C:\Users\Administrator.suprotim\AppData\LocalLow\Microsoft\Silverlight\is\
and the image is stored at:
C:\Users\Administrator.suprotim\AppData\LocalLow\Microsoft\Silverlight\is\dr0ccmrn.kwc\hr31wxjf.o0b\1\s\pdfknk1vnik4d0hl4zegmkxtszihwwgfemh5c4j4pphxq2pfxeaaaaea\f
Now in the DisplayImage() method, we open the desired image from the IsolatedStorage into a FileStream and set this stream directly to populate the graphics source file for the BitmapImage using 'SetSource'. Finally, we set the img1.Source property to this instance of the BitmapImage and you get to see the image.
As mentioned earlier, the first time, the image is requested from the database. I have added some statements to display the status of the operation as shown below:
For all subsequent requests, the image is fetched from the IsolatedStorage
This article demonstrated how to use IsolatedStorage to store images locally, reduce the response time for subsequent requests and improve performance of the application overall. There are other options like caching BitmapImage objects in memory which also can improve responsiveness. I would advice to choose the method that best suits your requirements. The entire source code of this article can be downloaded from here. I hope this article was useful and I thank you for viewing it.
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Suprotim Agarwal, MCSD, MCAD, MCDBA, MCSE, is the founder of
DotNetCurry,
DNC Magazine for Developers,
SQLServerCurry and
DevCurry. He has also authored a couple of books
51 Recipes using jQuery with ASP.NET Controls and
The Absolutely Awesome jQuery CookBook.
Suprotim has received the prestigious Microsoft MVP award for Sixteen consecutive years. In a professional capacity, he is the CEO of A2Z Knowledge Visuals Pvt Ltd, a digital group that offers Digital Marketing and Branding services to businesses, both in a start-up and enterprise environment.
Get in touch with him on Twitter @suprotimagarwal or at LinkedIn