Search This Blog

Thursday, January 6, 2011

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 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 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>PictureHeaderTemplate>
                <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 will be as follows:
<head runat="server">
    <title>Asynchronous Imagetitle>
    <script type="text/javascript" language="javascript">
        function RetrievePicture(imgCtrl, picid)
        {  
            imgCtrl.onload = null;
            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.
<%@ 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;
        }
    } 
}

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.