Loading Images Asynchronously Inside an ASP.NET GridView
Retrieving and displaying images in a GridView is a time consuming task. If done synchronously, this task can at times test the user’s patience. One way to provide a good user experience is to load the images asynchronously. So when the GridView loads, we initially display a default image for the user to view. We then retrieve the actual images asynchronously from the database. In this article, we will see how to do so.
For readability purpose, I have not covered any validations in the sample. The article focuses on how to read images asynchronously from the database and display it in the GridView control. I assume you have some knowledge of creating ASP.NET 3.5 websites. We will be using Visual Studio 2008 for this article.
I am using a sample database that has been pre-populated with some data. You can replace this sample to target your own database and table. The definition of my table is as follows:
CREATE DATABASE [PictureAlbum]
GO
USE [PictureAlbum]
GO
CREATE TABLE Album
(
pic_id int IDENTITY NOT NULL,
picture_tag varchar(50),
pic image
)
Step 1: Create an ASP.NET website. Drag and drop a ‘SqlDataSource’ Control to the page and use the wizard to connect to the ‘PictureAlbum’ database. Select the pic_id, picture_tag columns from the Album table. The wizard will also prompt you to save the connection string in the web.config file. Choose to do so. The design code will look similar to the following:
<asp:SqlDataSource ID="SqlDataSource1" runat="server"
ConnectionString="<%$ ConnectionStrings:PictureAlbumConnectionString %>"
SelectCommand="SELECT [pic_id], [picture_tag] FROM [Album]">
</asp:SqlDataSource>
An entry will be added to the web.config file as shown below:
<connectionStrings>
<add name="PictureAlbumConnectionString" connectionString="Data Source=.;Initial Catalog=PictureAlbum;Integrated Security=True" providerName="System.Data.SqlClient"/>
</connectionStrings>
Step 2: Now add a GridView control to the page. Set its ‘AutoGenerateColumns’ property to false. Using the smart tag, select the DataSource to be ‘SqlDataSource1’ in the GridView tasks panel. Using the same panel, click on the Enable Paging checkbox. The source will look similar to the following.
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataSourceID="SqlDataSource1" AllowPaging="True">
<Columns>
<asp:BoundField DataField="pic_id" HeaderText="Picture ID" InsertVisible="False"
ReadOnly="True" SortExpression="pic_id" />
<asp:BoundField DataField="picture_tag" HeaderText="Tags"
SortExpression="picture_tag" />
</Columns>
</asp:GridView>
<asp:SqlDataSource ID="SqlDataSource1" runat="server"
ConnectionString="<%$ ConnectionStrings:PictureAlbumConnectionString %>"
SelectCommand="SELECT [pic_id], [picture_tag] FROM [Album]">
</asp:SqlDataSource>
Step 3: We will now add an <ItemTemplate> to the GridView to display the images. When the GridView is displayed to the user, we will be showing a default image (using the ‘src’ attribute of the image) which will be quickly downloaded and displayed to the user. Then using the onLoad() event of the image, we will retrieve the actual image from the database. The onLoad() will point to a javascript function which will give a call to an ‘image handler’ to retrieve the image from the database. It is this javascript function called ‘RetrieveImage()’ which does the part of performing the operation asynchronously. The <ItemTemplate> code will look similar to the following:
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
DataSourceID="SqlDataSource1" AllowPaging="True">
<Columns>
<asp:BoundField DataField="pic_id" HeaderText="pic_id" InsertVisible="False"
ReadOnly="True" SortExpression="pic_id" />
<asp:BoundField DataField="picture_tag" HeaderText="picture_tag"
SortExpression="picture_tag" />
<asp:TemplateField>
<HeaderTemplate>Picture</HeaderTemplate>
<ItemTemplate>
<img border="1" src="images/cursor.jpg" onerror="this.src='images/error.jpg'" onload="RetrievePicture(this,'<%# Eval("pic_id")%>');"/>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
The javascript function declared in the <head> will be as follows:
<head runat="server">
<title>Asynchronous Image</title>
<script type="text/javascript" language="javascript">
function RetrievePicture(imgCtrl, picid)
{
imgCtrl.src = 'ShowImage.ashx?id=' + picid;
}
</script>
</head>
This function accepts a reference to the image control and the picture id, that is passed using the ‘Eval(“pic_id”)’ expression. The javascript function then gives a call to an image handler covered in the next step. The function passes the picture id to the handler and receives the image associated with the handler, which it binds to the image using 'imgCtrl.src'.
Step 4: In order to display the image on the page, we will create an Http handler. To do so, right click project > Add New Item > Generic Handler > ShowImage.ashx. Add the code shown below to the handler.
C#
<%@ WebHandler Language="C#" Class="ShowImage" %>
using System;
using System.Configuration;
using System.Web;
using System.IO;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Drawing.Imaging;
using System.ComponentModel;
public class ShowImage : IHttpHandler
{
long seq = 0;
byte[] empPic = null;
public void ProcessRequest(HttpContext context)
{
Int32 picid;
if (context.Request.QueryString["id"] != null)
picid = Convert.ToInt32(context.Request.QueryString["id"]);
else
throw new ArgumentException("No parameter specified");
// Convert Byte[] to Bitmap
Bitmap newBmp = ConvertToBitmap(ShowAlbumImage(picid));
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[] ShowAlbumImage(int picid)
{
string conn = ConfigurationManager.ConnectionStrings["PictureAlbumConnectionString"].ConnectionString;
SqlConnection connection = new SqlConnection(conn);
string sql = "SELECT pic FROM Album WHERE Pic_ID = @ID";
SqlCommand cmd = new SqlCommand(sql, connection);
cmd.CommandType = CommandType.Text;
cmd.Parameters.AddWithValue("@ID", picid);
try
{
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;
}
catch
{
return null;
}
finally
{
connection.Close();
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
VB.NET
<%@ WebHandler Language="vb" Class="ShowImage" %>
Imports System
Imports System.Configuration
Imports System.Web
Imports System.IO
Imports System.Data
Imports System.Data.SqlClient
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.ComponentModel
Public Class ShowImage
Implements IHttpHandler
Private seq As Long = 0
Private empPic() As Byte = Nothing
Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
Dim picid As Int32
If context.Request.QueryString("id") IsNot Nothing Then
picid = Convert.ToInt32(context.Request.QueryString("id"))
Else
Throw New ArgumentException("No parameter specified")
End If
' Convert Byte[] to Bitmap
Dim newBmp As Bitmap = ConvertToBitmap(ShowAlbumImage(picid))
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 ShowAlbumImage(ByVal picid As Integer) As Byte()
Dim conn As String = ConfigurationManager.ConnectionStrings("PictureAlbumConnectionString").ConnectionString
Dim connection As New SqlConnection(conn)
Dim sql As String = "SELECT pic FROM Album WHERE Pic_ID = @ID"
Dim cmd As New SqlCommand(sql, connection)
cmd.CommandType = CommandType.Text
cmd.Parameters.AddWithValue("@ID", picid)
Try
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
Catch
Return Nothing
Finally
connection.Close()
End Try
End Function
Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return False
End Get
End Property
End Class
The steps for retrieving the image from the database, using the handler are as follows:
1. The ID is passed to the handler via query string. We use the Request.QueryString[“id”] to retrieve the PictureID(pic_id) from the handler url. The ID is then passed to the ‘ShowAlbumImage()’ 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 newBmp.Save(context.Response.OutputStream, ImageFormat.Jpeg)
That’s it. Run the application. When the application loads initially, it displays a default image as shown below. Please use a nice 'progress bar like' logo in your application and apologies for this roughed up image shown below.
The actual images are then loaded one by one from the database using the image handler as shown below:
Using the technique discussed in this article, you can use a little bit of javascript in your asp.net page to retrieve the image asynchronously from the database. Not only does it provide a good user experience, but it also enables you to have control over the images being retrieved. The source code of this article in C# and VB.NET can be downloaded from here. I hope this article was useful and I thank you for viewing it.