Introduction
This article describes several techniques on how to create a simple photo album and slideshow:
- Save and retrieve images from a MS SQL Server database
- Resize images
- Retrieve images from the database using AJAX
XmlHttpRequest
- Use the AJAX
UpdatePanel
and ScriptManager
to retrieve images from a database without refreshing the web page
Background
The AjaxPhotoAlbum project was developed to show several useful techniques:
- The sample photo album consists of thumbnails and an image control to browse the large image.
When the user moves mouse over a thumbnail image, the large image also gets populated with the selected image. The large image is retrieved from the database with the AJAX XmlHttpRequest
using the web page GetSelectedImage.aspx.
- The user can also click "Previous" (<<) and "Next" (>>) link buttons to get the previous / next image in a large image placeholder. The whole control is placed inside an AJAX
UpdatePanel
, and because of that, even if the images are retrieved from the database on postback events, the web page does not get refreshed, and only the large image control gets updated with the new image. - The user can also run the slide show that will retrieve images from the database in an interval selected from an Interval drop down list control. In this case, an AJAX
XmlHttpRequst
object is used to send requests and get the responses using the web page behind GetSelectedImage.aspx. - The images for the photo album are stored in a database. To add an image to a database, the user can click the "Add New Image" button. The original image is resized to two different sizes - small (120 px width - used for thumbnails) and large (250 px width - used for the large image placeholder) and saved to the "Images" table in a database.
- When an image is required to be retrieved from the database (either for a thumbnail or large image control), it is read into a byte array and then is written to the web page "ImagePage.aspx", the link to which in turn is used as "
src
" attribute of the image control on a web page.
Using the code
For this project, an MS SQL Server database "AjaxPhotoAlbum" with just one table "Images" was created. The script to create the table can be found in the download package, but below is the description of the "Images" table:
Column Name | Data Type |
ImageId | int (PK, Identity Seed = 1) |
ImageSmall | image |
ImageLarge | image |
This table will hold the images for the AjaxPhotoAlbum project. The connection string from the web.config file is shown here:
<appSettings>
<add key="DSN"
value="server=localhost;Integrated Security=SSPI;database=AjaxPhotoAlbum"/>
</appSettings>
The complete project structure includes four layers:
- Presentation Layer: Default.aspx
- GetSelectedImage.aspx (the web page that provides actual post backs in AJAX
XmlHttpRequest
calls) - ImagePage.aspx (the web page that presents the image retrieved from the database in an image control)
- WebControls:
- PhotoAlbumControl.ascx
- SlideShow.ascx
- JavaScript
- Images
- CSS
- Business Layer: App_Code
BL
- Data Access Layer: App_Code
DAL
- MS SQL Server database: The AjaxPhotoAlbum database
PhotoAlbumControl.ascx is placed on the Default.aspx web page inside an AJAX UpdatePanel
. Default.aspx also has a ScriptManager
control. In order to make AJAX controls work, it is necessary to have the <httpHandlers>
section under <system.web>
node.
<httpHandlers>
<remove verb="*" path="*.asmx"/>
<add verb="*" path="*.asmx" validate="false"
type="System.Web.Script.Services.ScriptHandlerFactory,
System.Web.Extensions, Version=1.0.61025.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add verb="*" path="*_AppService.axd"
validate="false"
type="System.Web.Script.Services.ScriptHandlerFactory,
System.Web.Extensions, Version=1.0.61025.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add verb="GET,HEAD" path="ScriptResource.axd"
type="System.Web.Handlers.ScriptResourceHandler,
System.Web.Extensions, Version=1.0.61025.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35"
validate="false"/>
</httpHandlers>
With the above, when the user clicks "Previous" /"Next" buttons ("<<" or ">>"), the web page is posted back but is not refreshed as a whole. Only the imgPhoto
web control is refreshed with the new selected image. The thumbnails are built as a DataList
control. The images IDs are retrieved from the database, and on the DataList ItemDataBound()
event, the thumbnail images attribute "src
", "onclick
", and "onmouseover
" - are being populated.
protected void ThumbnailsItemDatabound(object sender, DataListItemEventArgs e)
{
Image tImage = (Image)dlThumbnails.Controls[e.Item.ItemIndex].Controls[3];
string ajaxServerUrl = "GetSelectedImage.aspx";
tImage.Attributes.Add("src", "ImagePage.aspx?s=s&id=" +
((DataRowView)e.Item.DataItem).Row.ItemArray[0].ToString());
tImage.Attributes.Add("onclick",
"getImage(‘" + ajaxServerUrl + "‘," +
((DataRowView)e.Item.DataItem).Row.ItemArray[0].ToString() + ")");
tImage.Attributes.Add("onmouseover", "getImage(‘" +
ajaxServerUrl + "‘," +
((DataRowView)e.Item.DataItem).Row.ItemArray[0].ToString() + ")");
}
The "onclick
" and "onmouseover
" thumbnail image attributes are called in the AJAX JavaScript function getImage()
to populate the large image control impPhoto
with the image selected in the thumbnails. The getImage()
JavaScript function sends the AJAX XmlHttpRequest
to the web page GetSelectedImage.aspx, which in turns does the actual postback to retrieve the requested image ID from the database.
function getImage(AjaxServerUrl, id)
{
var url = AjaxServerUrl + "?id=" + id
imageClient.open("GET", url);
imageClient.onreadystatechange = getImageBack;
imageClient.send(null);
}
The GetSelectedImage.aspx web page retrieves the required image ID and returns it as a response in XML format.
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using Tirex;
public partial class GetSelectedImage : System.Web.UI.Page
{
DataSet ds = new DataSet();
protected void Page_Load(object sender, EventArgs e)
{
Response.Clear();
Response.ContentType = "text/xml";
int id = 0;
if (Request.Params["id"] != null)
{
id = Int32.Parse(Request.Params["id"].ToString());
}
else
{
if (Session["ImageId"] != null)
{
id = Int32.Parse(Session["ImageId"].ToString());
}
id = ImageClass.GetImageId(id);
Session["ImageId"] = id.ToString();
}
Session["SelectedImageId"] = id.ToString();
Response.Write("<Root>");
Response.Write("<ImageId>");
Response.Write(id);
Response.Write("</ImageId>");
Response.Write("</Root>");
}
}
The response is in turn received and parsed by the getImageBack()
JavaScript function. Please, consider the differences on how the response is processed for Internet Explorer and Firefox.
function getImageBack(response)
{
var imageId = 0;
try
{
if(imageClient.readyState == COMPLETE && imageClient.status == OK)
{
if (document.implementation.createDocument)
{
var xmlElementImageId =
imageClient.responseXML.getElementsByTagName(
"Root")[0].childNodes[0];
imageId = xmlElementImageId.textContent;
}
else if (document.all)
{
xmlDocument = new ActiveXObject(‘Microsoft.XMLDOM‘);
xmlDocument.async = false;
xmlDocument.loadXML(imageClient.responseText);
var xmlElementImageId =
xmlDocument.selectSingleNode(‘Root/ImageId‘);
imageId = xmlElementImageId.text;
}
var imgPhoto;
imgPhoto = window.document.getElementById(
"PhotoAlbumControl1_imgPhoto");
imgPhoto.src = "ImagePage.aspx?s=l&id=" + imageId;
}
}
catch(err)
{
alert(err.message);
}
}
The images are retrieved from the database as an array of bytes, and are written to the Image.aspx web page, with in turn is used as an "src
" attribute for the thumbnails image or the imgPhoto
image control.
private void Page_Load(object sender, System.EventArgs e)
{
string sSQL = "";
SqlDataReader drData = null;
SqlCommand cmd = null;
SqlConnection cnn = new SqlConnection(SqlHelper.connectionString());
cnn.Open();
string id = Request.Params["id"].ToString();
string s = Request.Params["s"].ToString();
if (s == "s")
{
sSQL = "SELECT ImageSmall FROM Images WHERE ImageId=" + id.ToString();
}
else
{
sSQL = "SELECT ImageLarge FROM Images WHERE ImageId=" + id.ToString();
}
using (cmd = new SqlCommand())
{
cmd.CommandType = CommandType.Text;
cmd.CommandText = sSQL;
cmd.Connection = cnn;
drData = cmd.ExecuteReader();
if (drData.Read())
{
if (s == "s")
{
Response.BinaryWrite((byte[])drData["ImageSmall"]);
}
else
{
Response.BinaryWrite((byte[])drData["ImageLarge"]);
}
}
cnn.Close();
}
}
The user can add new images to the photo album using the "Add New Image" button. To save the images into the database, they are first read into the array of bytes.
len1 = File1.PostedFile.ContentLength;
imageSmall = new byte[len1];
imageLarge = new byte[len1];
File1.PostedFile.InputStream.Read(imageLarge, 0, len1);
Just before being saved into the database, the posted image is resized to the required sizes.
imageSmall = ImageClass.ResizeImage(250, imageLarge);
imageLarge = ImageClass.ResizeImage(250, imageLarge);
public static byte[] ResizeImage(int newWidth, byte[] pictureBytes)
{
Bitmap b = (Bitmap)Bitmap.FromStream(new MemoryStream(pictureBytes));
int imageWidth = b.Width;
int imageHeight = b.Height;
double newHeightD = Convert.ToDouble(newWidth) /
Convert.ToDouble(imageWidth) * Convert.ToDouble(imageHeight);
int newHeight = Convert.ToInt32(newHeightD);
Bitmap output = new Bitmap(b, new Size(newWidth, newHeight));
MemoryStream ms = new MemoryStream();
output.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
byte[] bmpBytes = ms.GetBuffer();
output.Dispose();
ms.Close();
return bmpBytes;
}
The live sample can viewed at http://www.realproject.ca/default.aspx?contentid=1660.