Silverlight 2 - Saving and Retrieving Images from IsolatedStorage

Posted by: Suprotim Agarwal , on 11/22/2008, in Category Silverlight 2, 3, 4 and 5
Views: 39118
Abstract: 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 a quicker approach where we will use the IsolatedStorage to store and retrieve images.
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:
From DB
For all subsequent requests, the image is fetched from the IsolatedStorage
From Isolated
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.
Give a +1 to this article if you think it was well written. Thanks!
Recommended Articles
Suprotim Agarwal, ASP.NET Architecture MVP, MCSD, MCAD, MCDBA, MCSE, is the CEO of A2Z Knowledge Visuals Pvt. He primarily works as an Architect Consultant and provides consultancy on how to design and develop .NET centric database solutions.

Suprotim is the founder and primary contributor to DotNetCurry, DNC .NET Magazine, SQLServerCurry and DevCurry. He has also written an EBook 51 Recipes using jQuery with ASP.NET Controls. and authored a new one at The Absolutely Awesome jQuery CookBook.

Follow him on twitter @suprotimagarwal


Page copy protected against web site content infringement by Copyscape


User Feedback
Comment posted by gail on Tuesday, September 15, 2009 1:50 AM
I JUST ACCIDENTLY RIGHT CLICKED "BLOCKIMAGEGES FROM C3.(C2.) AC-IMAGES. MY AND ALL KINDS OF RANDO PICK ARE NOT SHOWING UP. hOW DO I FIX IT IN LAMENS TERMS?
Comment posted by Jinesh on Thursday, March 4, 2010 12:50 AM
Nice article, but here, not mentioned how can we convert the bitmap image to
byte array, using silverlight2.
Comment posted by salesforceintegrator.com on Thursday, July 26, 2012 4:45 AM
nice post ,quite helpful for my requirement .... www.salesforceintegrator.com

Post your comment
Name:  
E-mail: (Will not be displayed)
Comment:
Insert Cancel