Click here to Skip to main content
Licence Apache
First Posted 24 Nov 2011
Views 39,406
Bookmarked 118 times

ASP .NET Image Slider with fancy thumbnail hover effect

This article aims to show that ASP .NET combined with jQuery/Ajax/Javascript produces user friendly and attractive websites.
   4.92 (42 votes)

1

2

3
2 votes, 4.8%
4
40 votes, 95.2%
5
4.92/5 - 42 votes
μ 4.92, σa 1.02 [?]
 

jQuery.ImageSlider.Model.PNG

YouTube.PNG

Introduction

This article aims to show that ASP .NET combined with jQuery/Ajax/Javascript produces user friendly and attractive websites.

In order to focus on the fancy thumbnail hover effect, I decided to make the application as simple as possible. I have used an XML document that contains the images paths and the Repeater server control with paging.

Browser compatibilty

All the versions available have been tested on IE7, IE8, IE9, Firefox, Chrome, Safari and Opera, and are browser compatible with IE7, IE8, IE9, Firefox, Chrome, Safari and Opera.

The directive <!DOCTYPE html> at the top of the Default.aspx page is very important to ensure that the page will be rendered properly.

Models

jQuery.ImageSlider1.PNG

jQuery.ImageSlider2.PNG

jQuery.ImageSlider4.PNG

jQuery.ImageSlider3.PNG

Using the code

The XML structure is defined as bellow.

<images>
  <image imageUri="/Images/DSC_0349_k.JPG" thumbnailUri="/Images/DSC_0349_k_thumb.JPG" />
  <image imageUri="/Images/DSC_0359_nb.JPG" thumbnailUri="/Images/DSC_0359_nb_thumb.JPG" />
</images>

Every image has a preview file whose location is set in the imageUri attribute and a thumbnail file whose location is set in the thumbnailUri attribute. You will notice that all the images are stored in the folder Images in the root of the Web site.

I decided to use the Repeater server control because it responds to my need. This control is a data-bound list that allows custom layout by repeating a specified template for each item displayed in the list.

<asp:Repeater ID="RepeaterImages" runat="server" EnableViewState="false">
    <HeaderTemplate>
        <ul class="thumb">
    </HeaderTemplate>
    <ItemTemplate>
        <li><a href='<%#DataBinder.Eval(Container.DataItem, "imageUri")%>'>
            <img src='<%#DataBinder.Eval(Container.DataItem, "thumbnailUri")%>' alt="" />
        </a></li>
    </ItemTemplate>
    <FooterTemplate>
        </ul>
    </FooterTemplate>
</asp:Repeater>

In the layout, I used an unordered list ul. Each item of the list contains the thumbnail and a link to its preview.

The binding is done by the method LoadImages.

private void LoadImages()
{
    // Populate the repeater control with the images DataSet
    // Indicate that the data should be paged
    // Set the number of images you wish to display per page
    // Set the PagedDataSource's current page
    var pds = new PagedDataSource
                    {
                        DataSource = ImagesDataView,
                        AllowPaging = true,
                        PageSize = 9,
                        CurrentPageIndex = CurrentPage - 1
                    };

    LabelCurrentPage.Text = "Page " + CurrentPage + " of " + pds.PageCount;

    // Disable Previous or Next buttons if necessary
    ImageButtonPrevious.Visible = !pds.IsFirstPage;
    ImageButtonNext.Visible = !pds.IsLastPage;

    // DataBind
    RepeaterImages.DataSource = pds;
    RepeaterImages.DataBind();
}

The current page number is persisted in the ViewState.

public int CurrentPage
{
    get
    {
        // Look for current page in ViewState
        object o = ViewState["CurrentPage"];
        if (o == null) return 1; // default page index of 1
        return (int) o;
    }

    set { ViewState["CurrentPage"] = value; }
}

The DataView is persisted in the Cache to boost paging performance.

public DataView ImagesDataView
{
    get
    {
        if (Cache["ImagesDataView"] == null)
        {
            // Read images info from XML document into a DataSet
            var d = new DataSet();
            d.ReadXml(MapPath("/App_Data/Images.xml"));
            Cache["ImagesDataView"] = d.Tables[0].DefaultView;
        }
        return (DataView)Cache["ImagesDataView"];
    }
}

The paging is done through two buttons and a label that contains the current page number.

<div id="pager">
    <asp:Label ID="LabelCurrentPage" runat="server" EnableViewState="false"></asp:Label>
    <asp:ImageButton ID="ImageButtonPrevious" runat="server" ImageUrl="Styles/Images/Arrow-Left-icon.png"
        OnClick="ImageButtonPrevious_Click" CssClass="previous" EnableViewState="false" />
    <asp:ImageButton ID="ImageButtonNext" runat="server" ImageUrl="Styles/Images/Arrow-right-icon.png"
        OnClick="ImageButtonNext_Click" CssClass="next" EnableViewState="false" />
</div>

If the page is being rendered for the first time or isn't being loaded in response to a PostBack, we load the images.

protected void Page_Load(object sender, EventArgs e)
{
    // Reload control if the page is being rendered for the first time
    // or isn't being loaded in response to a postback
    if (!IsPostBack)
    {
        LoadImages();
    }
}

When the user clicks on the next button, the current page number is updated and the images are loaded.

protected void ImageButtonNext_Click(object sender, EventArgs e)
{
    // Set viewstate variable to the next page
    CurrentPage++;

    // Reload control
    LoadImages();
}

When the user clicks on the previous button, the current page number is updated and the images are loaded.

protected void ImageButtonPrevious_Click(object sender, EventArgs e)
{
    // Set viewstate variable to the previous page
    CurrentPage--;

    // Reload control
    LoadImages();
}

The icon buttons that I used are free and from Icon Archive.

The images that I used are free and from morgueFile.

You will notice that I disabled the SessionState because I don't need it and for performance reasons If you don't need it is recommended to disable it.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="jQuery.ImageSlider.Default" EnableSessionState="false" %> 

Description of the Update 1

I had a feedback on my blog and on codeproject to display image metadata when the end-user clicks on the thumbnail. I decided to update the article to take this feedback in consideration.

In the version without metadata, Loading the images from the XML into a DataSet was sufficient. However, now that we need to display the metadata in the UI we have to retrieve it from the image file and encapsulate all the image informations into an object then update the binding of the images Repeater.

Models

sc0.jpg

sc2.PNG

sc3.PNG

sc5.jpg

Loading the image metadata

I used MediaInfo.dll to load the metadata from the image file. MediaInfo supplies technical and can read tag/atom information about a video, audio or image file containers.

MediaInfo is cross-platform (Win32, Linux, and Mac), open source code (GPL/LGPL dual-license), and can be compiled into a .dll on Windows, .so (Shared Object) on Linux, .dynlib (Dynamic Library) on Mac.

MediaInfo is developed in C++ but there is a C# Wrapper in the source code of the library. The dll have to be placed in the application bin folder.

I created a new class named Metadata to encapsulate the metadata Key and Value.

namespace jQuery.ImageSlider
{
    public class Metadata
    {

        #region Constructor

        public Metadata(string key, string value)
        {
            Key = key;
            Value = value;
        }

        #endregion

        #region Properties

        public string Key { get; private set; }
        public string Value { get; set; }

        #endregion

    }
}  

I also created a new class named Image to encapsulate image informations.

    public class Image
    {
        #region Constructor

        public Image(int id, string previewPath, string thumbnailPath, bool loadMetadata, bool isRelativePaths)
        {
            Id = id;
            PreviewPath = previewPath;
            ThumbnailPath = thumbnailPath;
            IsRelativePaths = isRelativePaths;

            if (loadMetadata)
            {
                LoadMetadata();
            }
            else
            {
                Metadata = new List<Metadata>();
                IsMetadataLoaded = false;
            }
        }

        #endregion

        #region Properties

        public int Id { get; private set; }
        public string PreviewPath { get; private set; }
        public string ThumbnailPath { get; private set; }
        public bool IsRelativePaths { get; private set; }
        public IList<Metadata> Metadata { get; private set; }
        public bool IsMetadataLoaded { get; private set; }

        #endregion

        #region Methods

        public void LoadMetadata()
        {
            // If HttpContext is null and paths are relative, Throw an exception
            if (IsRelativePaths && HttpContext.Current == null)
            {
                throw new Exception("HttpContext is null, you cannot use relative paths.");
            }

            // Load the metadata from the preview file
            MediaInfo mediaInfo = new MediaInfo();
            mediaInfo.Open(IsRelativePaths ? HttpContext.Current.Server.MapPath(PreviewPath) : PreviewPath);
            mediaInfo.Option("Complete");
            string s = mediaInfo.Inform();

            // Parse the metadata
            var lines = s.Split('\n');
            Metadata = lines.
                Select(l => l.Split(':')).
                Where(a => a.Length == 2).
                Select(a => new Metadata(a[0].Trim(), a[1].Trim())).
                ToList();

            IsMetadataLoaded = true;
        }

        #endregion
    }
}

The LoadMetadata method loads the metadata of the image. MediaInfo outputs a string that contains all the informations of the image. I had to parse it to retrieve the metadata and set the Metadata property.

Data Binding

Now that we used a class for the images, the DataSource of the images Repeater changed. Here we parse the XML and load a collection of images without metadata. The metadata will be loaded lazily as you will see.

// Use ASP .NET Cache to boost paging performance
protected IList<Image> Images
{
    get
    {
        if (Cache["Images"] == null)
        {
            // Read images info from XML document
            XDocument xdoc = XDocument.Load(MapPath("/App_Data/Images.xml"));
            IList<Image> images = xdoc.Descendants("image").
                                    Select(i =>
                                        new Image(int.Parse(i.Attribute("id").Value),
                                                            i.Attribute("imageUri").Value,
                                                            i.Attribute("thumbnailUri").Value,
                                                            false,
                                                            true)).ToList();

            Cache["Images"] = images;
        }
        return (IList<Image>)Cache["Images"];
    }
}

In the version without metadata, I handled the preview view with jQuery so I used the hyperlink HTML tag because I didn't need to do postback. But, now that we want to feed another view (Metadata), we need to do a postback when the end-user clicks on the thumbnail in order to load the metadata in the view.

<asp:Repeater ID="RepeaterImages" runat="server" OnItemCommand="RepeaterImages_ItemCommand">
    <HeaderTemplate>
        <ul class="thumb">
    </HeaderTemplate>
    <ItemTemplate>
        <li>
            <asp:LinkButton ID="LinkButtonThumbnail" runat="server" CommandName="ShowPreview"
                CommandArgument='<%#DataBinder.Eval(Container.DataItem, "Id")%>'>
                <img src='<%#DataBinder.Eval(Container.DataItem, "ThumbnailPath")%>' alt="" />
            </asp:LinkButton>
        </li>
    </ItemTemplate>
    <FooterTemplate>
        </ul>
    </FooterTemplate>
</asp:Repeater> 

I used a Repeater server control for the metadata view. I used the AlternatingItemTemplate in order to set up a custom layout for the even rows and a custom layout for the odd rows. An alternative would be to use the ItemTemplate only and set up the layout by using the CSS odd and even rules.

<div id="metadata" runat="server">
    <img src="Styles/Images/asset-green-16.png" alt="" />
    <span class="metadataHeader">Image information</span>
    <asp:Repeater ID="RepeaterMetadata" runat="server">
        <HeaderTemplate>
            <table cellpadding="0" cellspacing="0">
        </HeaderTemplate>
        <ItemTemplate>
            <tr>
                <td class="metadataName">
                    <%# DataBinder.Eval(Container.DataItem, "Key") %>
                </td>
                <td class="metadataValue">
                    <%# DataBinder.Eval(Container.DataItem, "Value") %>
                </td>
            </tr>
        </ItemTemplate>
        <AlternatingItemTemplate>
            <tr>
                <td class="metadataNameAlt">
                    <%# DataBinder.Eval(Container.DataItem, "Key") %>
                </td>
                <td class="metadataValueAlt">
                    <%# DataBinder.Eval(Container.DataItem, "Value") %>
                </td>
            </tr>
        </AlternatingItemTemplate>
        <FooterTemplate>
            </table>
        </FooterTemplate>
    </asp:Repeater>
</div> 

The header image that I used is under Creative Commons Attribution Share Alike license and is from SweetiePlus.

The method LoadImages has changed a little bit. Indeed, Now we use an IList<Image> as DataSource and the metadata view has to be hidden when the method is called.

private void LoadImages()
{
    // Populate the repeater control with the images collection
    // Indicate that the data should be paged
    // Set the number of images you wish to display per page
    // Set the PagedDataSource's current page
    var pds = new PagedDataSource
    {
        DataSource = Images,
        AllowPaging = true,
        PageSize = 9,
        CurrentPageIndex = CurrentPage - 1
    };

    LabelCurrentPage.Text = "Page " + CurrentPage + " of " + pds.PageCount;

    // Hide Previous or Next buttons if necessary
    ImageButtonPrevious.Visible = !pds.IsFirstPage;
    ImageButtonNext.Visible = !pds.IsLastPage;

    // Hide the metadata view
    metadata.Visible = false;

    // Bind data to the Images Repeater
    RepeaterImages.DataSource = pds;
    RepeaterImages.DataBind();
}

You will notice that I used the ItemCommand event in the images Repeater server control in order to feed the metadata view when the user clicks on the thumbnail. I also removed the jQuery code that shows the main view because now it is handled by the ItemCommand.

protected void RepeaterImages_ItemCommand(object source, RepeaterCommandEventArgs e)
{
    if (e.CommandName.Equals("ShowPreview"))
    {
        // Retrieve the image Id from the command argument
        int imageId = int.Parse(e.CommandArgument.ToString());

        // Load the selected image
        Image image = Images.First(i => i.Id == imageId);

        // Load the Preview
        main_view.Controls.Add(new WebControls.Image { ImageUrl = image.PreviewPath });
        main_view.Visible = true;

        // Load the Metadata
        LoadMetadata(image);
        metadata.Visible = true;
    }
} 

The LoadMetadata method binds the metadata Repeater Lazily. In other words, we only load the metadata of the selected image when the user clicks on its thumbnail and once it is loaded we never load it again.

private void LoadMetadata(Image image)
{
    // Load the metadata lazily
    if (!image.IsMetadataLoaded)
    {
        image.LoadMetadata();
    }

    // Bind data to the Metadata Repeater
    RepeaterMetadata.DataSource = image.Metadata;
    RepeaterMetadata.DataBind();
} 

The paging didn't change.

Description of the Update 2

I thought It would be intersting to use Ajax in the page. With the UpdatePanel server control, we can display an animation while the metadata and the preview are being loaded.

In this update, I used the UpdatePanel server control, the UpdateProgress server control and updated both client side code and server side code.

Models

sc0.jpg

sc1.jpg

sc2.jpg

sc3.jpg

sc4.jpg

sc5.jpg

Client side

I used the UpdatePanel and UpdateProgress server controls in the page.

<form id="form1" 
      runat="server">
<asp:ScriptManager ID="ScriptManager1" 
                   runat="server">
</asp:ScriptManager>
<asp:UpdatePanel ID="UpdatePanelContainer" 
                 runat="server">
    <ContentTemplate>
        <div class="container">
            <asp:Repeater ID="RepeaterImages" 
                          runat="server" 
                          OnItemCommand="RepeaterImages_ItemCommand">
                <HeaderTemplate>
                    <ul class="thumb">
                </HeaderTemplate>
                <ItemTemplate>
                    <li>
                        <asp:LinkButton ID="LinkButtonThumbnail" 
                                        runat="server" 
                                        CommandName="ShowPreview"
                                        CommandArgument='<%#DataBinder.Eval(Container.DataItem, "Id")%>'>
                            <img src='<%#DataBinder.Eval(Container.DataItem, "ThumbnailPath")%>' 
                                 alt="" />
                        </asp:LinkButton>
                    </li>
                </ItemTemplate>
                <FooterTemplate>
                    </ul>
                </FooterTemplate>
            </asp:Repeater>
            <div id="pager">
                <asp:Label ID="LabelCurrentPage" 
                           runat="server"></asp:Label>
                <asp:ImageButton ID="ImageButtonPrevious" 
                                 runat="server" 
                                 ImageUrl="Styles/Images/Arrow-Left-icon.png"
                                 OnClick="ImageButtonPrevious_Click" 
                                 CssClass="previous" />
                <asp:ImageButton ID="ImageButtonNext" 
                                 runat="server" 
                                 ImageUrl="Styles/Images/Arrow-right-icon.png"
                                 OnClick="ImageButtonNext_Click" 
                                 CssClass="next" />
            </div>
            <div id="main_view" 
                 runat="server">
            </div>
            <div id="metadata" 
                 runat="server">
                <img src="Styles/Images/asset-green-16.png" 
                     alt="" />
                <span class="metadataHeader">Image information</span>
                <asp:Repeater ID="RepeaterMetadata" 
                              runat="server">
                    <HeaderTemplate>
                        <table cellpadding="0" 
                               cellspacing="0">
                    </HeaderTemplate>
                    <ItemTemplate>
                        <tr>
                            <td class="metadataName">
                                <%# DataBinder.Eval(Container.DataItem, "Key") %>
                            </td>
                            <td class="metadataValue">
                                <%# DataBinder.Eval(Container.DataItem, "Value") %>
                            </td>
                        </tr>
                    </ItemTemplate>
                    <AlternatingItemTemplate>
                        <tr>
                            <td class="metadataNameAlt">
                                <%# DataBinder.Eval(Container.DataItem, "Key") %>
                            </td>
                            <td class="metadataValueAlt">
                                <%# DataBinder.Eval(Container.DataItem, "Value") %>
                            </td>
                        </tr>
                    </AlternatingItemTemplate>
                    <FooterTemplate>
                        </table>
                    </FooterTemplate>
                </asp:Repeater>
            </div>
        </div>
    </ContentTemplate>
</asp:UpdatePanel>
<asp:UpdateProgress ID="UpdateProgress" 
                    runat="server" 
                    AssociatedUpdatePanelID="UpdatePanelContainer">
    <ProgressTemplate>
        <div class="throbber">
            <img src="Styles/Images/ajax-loader.gif" 
                 alt="" />
        </div>
    </ProgressTemplate>
</asp:UpdateProgress>
</form> 

Now that we used the UpdatePanel we have to update the jQuery code. Indeed, the $(document).ready() function will work only the first time when the page is viewed and after that the fancy thumbnail hover disappears. You can easily see that by using the $(document).ready() and clicking on the next page for example, The fancy thumbnail hover effect disappears. But don't worry there is a solution to this issue. I used the pageLoad() Ajax function. pageLoad() is called after every UpdatePanel refresh and that is exactly what we were looking for. I advise you to take a look at this article regarding the Ajax pageLoad() function and the jQuery $(document).ready() function.

function pageLoad() {
    // Larger thumbnail preview 
    // hoverIntent
    window.$("ul.thumb li").hover(function () {
        window.$(this).css({ 'z-index': '10' });
        window.$(this).find('img').addClass('hover').stop()
            .animate({
                marginTop: '-110px',
                marginLeft: '-110px',
                top: '50%',
                left: '50%',
                width: '174px',
                height: '174px',
                padding: '20px'
            }, 200);

    }, function () {
        window.$(this).css({ 'z-index': '0' });
        window.$(this).find('img').removeClass('hover').stop()
            .animate({
                marginTop: '0',
                marginLeft: '0',
                top: '0',
                left: '0',
                width: '100px',
                height: '100px',
                padding: '5px'
            }, 400);
    });
} 

When the cursor is over an li we add the hover intent effect to the thumbnail by adding the hover CSS class to the img element and animating the image by using the animate jQuery function. When the cursor leaves the thumbnail we remove the hover CSS class and stop the animation of the image by using the animate jQuery function. The hover class is defined below.

ul.thumb li img.hover
{
    background: url(/Styles/Images/thumb_bg.png) no-repeat center center;
    border: none;
}

The CSS style of the ul element is defined below.

ul.thumb
{
    float: left;
    list-style: none;
    margin: 0;
    padding: 10px;
    width: 360px;
}

ul.thumb li
{
    margin: 0;
    padding: 5px;
    float: left;
    position: relative;
    width: 110px;
    height: 110px;
}

ul.thumb li img
{
    width: 100px;
    height: 100px;
    border: 1px solid #ddd;
    padding: 5px;
    background: #f0f0f0;
    position: absolute;
    left: 0;
    top: 0;
    -ms-interpolation-mode: bicubic;
}
It is also possible to set up this effect whithout using jQuery and just by using CSS3 transitions module.
I used the UpdateProgress server control to show an animation when the metadata and the preview are being loaded.
<asp:UpdateProgress ID="UpdateProgress" runat="server" AssociatedUpdatePanelID="UpdatePanelContainer">
    <ProgressTemplate>
        <div class="throbber">
            <img src="Styles/Images/ajax-loader.gif" alt="" />
        </div>
    </ProgressTemplate>
</asp:UpdateProgress> 
The animation gif that I used is generated automaticly from ajaxload. The CSS code is very basic. The throbber div is displayed in all the page with a transparent color and the ajaxload image is centered.
.throbber
{
    padding: 0;
    margin: 0;
    position: fixed;
    top: 0px;
    right: 0px;
    bottom: 0px;
    left: 0px;
    background: rgba(255, 255, 255, 0.5);
    z-index: 1;
}
.throbber img
{
    position: absolute;
    top: 50%;
    left: 50%;
    margin-top: -9px;
    margin-left: -110px;
    z-index: 2;
}

Server side

I updated the Images Repeater ItemCommand's event to show you that the animation is working fine for one second. In a release version you should remove the Thread.Sleep. The animation would be very useful If you have a huge amount of data to process in the server side.
protected void RepeaterImages_ItemCommand(object source, RepeaterCommandEventArgs e)
{
    if (e.CommandName.Equals("ShowPreview"))
    {

        // Show the Throbber one second
        // This line is here just to show you
        // That the Update Progress control is 
        // working fine
        // In a release version you should remove this line
        // The animation would be very useful
        // When your server call last more than 1 second
        Thread.Sleep(1000);

        // Retrieve the image Id from the command argument
        int imageId = int.Parse(e.CommandArgument.ToString());

        // Load the selected image
        Image image = Images.First(i => i.Id == imageId);

        // Load the Preview
        main_view.Controls.Add(new WebControls.Image { ImageUrl = image.PreviewPath });
        main_view.Visible = true;

        // Load the Metadata
        LoadMetadata(image);
        metadata.Visible = true;
    }
}  

Description of the Update 3

In this update I added a download button in the image's information box in order to offer to the end-user the ability to download the preview image.

I used an HttpHandler and persisted the current preview file path in the session state. I updated both server side code and client side code.

Models


scd1.JPG

scd2.JPG

scd3.JPG

scd4.JPG

scd5.JPG

scd6.JPG

scd7.JPG

Server side

I added a new property in the Default page called CurrentPreviewPath in order to persist the path of the current image in the session state.

protected string CurrentPreviewPath
{
    get
    {
        // Look for current image Path in Session State
        object o = Session["CurrentPreviewPath"];
        if (o == null) return string.Empty;
        return (string)o;
    }

    set { Session["CurrentPreviewPath"] = value; }
} 

The value of this property is updated when the end-user clicks on the thumbnail.

protected void RepeaterImages_ItemCommand(object source, RepeaterCommandEventArgs e)
{
    if (e.CommandName.Equals("ShowPreview"))
    {
        // Retrieve the image Id from the command argument
        int imageId = int.Parse(e.CommandArgument.ToString());

        // Load the selected image
        Image image = Images.First(i => i.Id == imageId);

        // set the current preview path in the session state for the download HttpHandler
        CurrentPreviewPath = image.IsRelativePaths
                            ? MapPath(image.PreviewPath)
                            : image.PreviewPath;

        // Load the Preview
        main_view.Controls.Add(new WebControls.Image { ImageUrl = image.PreviewPath });
        main_view.Visible = true;

        // Load the Metadata
        LoadMetadata(image);
        metadata.Visible = true;
    }
} 
The HttpHandler is named DownloadHandler.

handler.JPG

In this handler, we look for the preview file path in the session state. You will notice that the HttpHandler implements IReadOnlySessionState interface in order to have the permission to read the session state.
public class DownloadHandler : IHttpHandler, IReadOnlySessionState
{
    #region Constants

    // Block of 1024 bytes
    const int _BLOCK_ = 1024;

    #endregion

    #region Properties

    public bool IsReusable
    {
        get
        {
            return true;
        }
    }

    #endregion

    #region Methods

    public void ProcessRequest(HttpContext context)
    {
        // Look for the preview file path in the session state
        // You will notice that the handler implements IReadOnlySessionState interface
        // in order to have the permission to read the session state
        string imagePath = (string)context.Session["CurrentPreviewPath"];

        // Read file as binary values
        FileInfo imageFileInfo = new FileInfo(imagePath);
        using (FileStream imageFileStream = new FileStream(imagePath, FileMode.Open, 
                                                FileAccess.Read, FileShare.ReadWrite))
        using (BinaryReader br = new BinaryReader(imageFileStream))
        {
            try
            {
                // Get the Last update time and file name encoded
                string lastUpdate;
                var encodedData = new StringBuilder();
                encodedData.Append(HttpUtility.UrlEncode(imageFileInfo.Name, Encoding.UTF8));
                encodedData.Append(lastUpdate = imageFileInfo.LastWriteTimeUtc.ToString("r"));

                // Clean up the response and set the header informations
                context.Response.Clear();
                context.Response.Buffer = false;
                context.Response.AddHeader("Accept-Ranges", "bytes");
                context.Response.AppendHeader("ETag", "'" + encodedData + "'");
                context.Response.AppendHeader("Last-Modified", lastUpdate);
                context.Response.ContentType = "application/octet-stream";
                context.Response.AddHeader("Content-Disposition", "attachment;filename=" 
                                                                    + imageFileInfo.Name);
                context.Response.AddHeader("Content-Length", imageFileInfo.Length.ToString());
                context.Response.AddHeader("Connection", "Keep-Alive");

                // Set encoding to UTF8
                context.Response.ContentEncoding = Encoding.UTF8;

                // Send bytes
                br.BaseStream.Seek(0, SeekOrigin.Begin);

                // Dividing the data in _BLOCK_ bytes package
                long maxCount = (long)Math.Ceiling((imageFileInfo.Length + 0.0) / _BLOCK_);

                // Download in block of _BLOCK_ bytes
                for (long l = 0; l < maxCount && context.Response.IsClientConnected; l++)
                {
                    context.Response.BinaryWrite(br.ReadBytes(_BLOCK_));
                    context.Response.Flush();
                }
            }
            finally
            {
                // End the response
                context.Response.End();
            }
        }
    }

    #endregion

} 
To register the HttpHandler in the application we have to add these lines in the system.web section of the Web.config file.
<httpHandlers>
  <!--Register the DownloadHandler-->
  <add path="DownloadHandler.axd" verb="*" type="jQuery.ImageSlider.DownloadHandler, jQuery.ImageSlider" validate="true" />
</httpHandlers> 

Client Side

In the client side, I added a link element in the metadata div.
<a href="DownloadHandler.axd" class="download" title="Download">
    <img src="Styles/Images/Download-icon.png" alt="" />
</a> 
The download icon image that I used is from Icon Archive.

The button is placed at the top right of the box.
.download
{
    position: absolute;
    right: 0;
} 

Description of the Update 4

I had a feedback on my blog talking about displaying the thumbnails titles. I thought It would be intersting to set up that.

In this update I added a Div container for each li. This Div contains the image thumbnail link button and a Div that contains the title of the thumbnail. I also added a property Title to the Image class. Its value is the name of the file without extension. You can of course add a title attribute to the image node in the images XML file and retrieve the value from there.

I updated both server side code and client side code.

Models

sce1.JPG

sce2.JPG

sce3.JPG

sce4.JPG

sce5.JPG

sce6.JPG

sce7d.JPG

sce8d.JPG
sce9d.JPG

sce10.JPG

Server side

I added a property Title to the Image Class in order to store the image's title.

public class Image
{
    #region Constructors and Destructors

    public Image(
        int id, string title, string previewPath, string thumbnailPath, bool loadMetadata, bool isRelativePaths)
    {
        this.Id = id;
        this.Title = title;
        this.PreviewPath = previewPath;
        this.ThumbnailPath = thumbnailPath;
        this.IsRelativePaths = isRelativePaths;

        if (loadMetadata)
        {
            this.LoadMetadata();
        }
        else
        {
            this.Metadata = new List<Metadata>();
            this.IsMetadataLoaded = false;
        }
    }

    #endregion

    #region Public Properties

    public int Id { get; private set; }
    public bool IsMetadataLoaded { get; private set; }
    public bool IsRelativePaths { get; private set; }
    public IList<Metadata> Metadata { get; private set; }
    public string PreviewPath { get; private set; }
    public string ThumbnailPath { get; private set; }
    public string Title { get; private set; }

    #endregion

    #region Public Methods

    public void LoadMetadata()
    {
        // If HttpContext is null and paths are relative, Throw an exception
        if (this.IsRelativePaths && HttpContext.Current == null)
        {
            throw new Exception("HttpContext is null, you cannot use relative paths.");
        }

        // Load the metadata from the preview file
        var mediaInfo = new MediaInfo();
        mediaInfo.Open(
            this.IsRelativePaths ? HttpContext.Current.Server.MapPath(this.PreviewPath) : this.PreviewPath);
        mediaInfo.Option("Complete");
        string s = mediaInfo.Inform();

        // Parse the metadata
        string[] lines = s.Split('\n');
        this.Metadata =
            lines.Select(l => l.Split(':')).
            Where(a => a.Length == 2).
            Select(a => new Metadata(this.Id, a[0].Trim(), a[1].Trim())).
            ToList();

        this.IsMetadataLoaded = true;
    }

    #endregion
}

Thus, I updated the Images collection property in the code-behind of the Default page. I used the name of the image file without extension as a title. You can of course add a title attribute to the image node of the Images.xml file and use it instead.

// Use ASP .NET Cache to boost paging performance
private IList<Image> Images
{
    get
    {
        if (this.Cache["Images"] == null)
        {
            // Read images info from XML document
            XDocument xdoc = XDocument.Load(this.MapPath("/App_Data/Images.xml"));

            IList<Image> images = (from xe in xdoc.Descendants("image")
                                    let id = int.Parse(xe.Attribute("id").Value)
                                    let thumbnailUri = xe.Attribute("thumbnailUri").Value
                                    let previewUri = xe.Attribute("imageUri").Value
                                    let tite = Path.GetFileNameWithoutExtension(previewUri)
                                    select new Image(id, tite, previewUri, thumbnailUri, false, true)).
                                    ToList();

            this.Cache["Images"] = images;
        }
        return (IList<Image>)this.Cache["Images"];
    }
}

This is the only things that changed in the sever side code.

Client Side

I used a div container for the image in the RepeaterImages server control in order to show the image and its title on the client side. You can of course add any controls you want in this div depending on your needs.

<asp:Repeater ID="RepeaterImages" runat="server" OnItemCommand="RepeaterImages_ItemCommand">
    <HeaderTemplate>
        <ul class="thumb">
    </HeaderTemplate>
    <ItemTemplate>
        <li>
            <div class="imageContainer">
                <div class="imageTitle">
                    <%#DataBinder.Eval(Container.DataItem, "Title")%>
                </div>
                <asp:LinkButton ID="LinkButtonThumbnail" runat="server" CommandName="ShowPreview"
                                CommandArgument='<%#DataBinder.Eval(Container.DataItem, "Id")%>'>
                    <img src='<%#DataBinder.Eval(Container.DataItem, "ThumbnailPath")%>' alt="" />
                </asp:LinkButton>
            </div>
        </li>
    </ItemTemplate>
    <FooterTemplate>
    </ul>
    </FooterTemplate>
</asp:Repeater>

This is the only thing that changed in the Default.aspx page. The CSS code is described below.

ul.thumb
{
    float: left;
    list-style: none;
    margin: 0;
    padding: 10px;
    width: 360px;
}

ul.thumb li
{
    margin: 0;
    padding: 5px;
    float: left;
    position: relative;
    width: 110px;
    height: 110px;
}

ul.thumb li .imageContainer
{
    width: 100px;
    height: 100px;
    text-align: center;
    position: absolute;
    left: 0;
    top: 0;
    padding: 5px;
}

ul.thumb li .imageContainer .imageTitle
{
    margin: 0;
    width: 100%;
    white-space: nowrap;
    overflow: hidden;
}

ul.thumb li .imageContainer img
{
    width: 90px;
    height: 90px;
    background: #f0f0f0;
    -ms-interpolation-mode: bicubic;
    padding: 5px;
}

/* Change the image container background on mouse hover */
ul.thumb li .imageContainer.hover
{
    background: url(/Styles/Images/thumb_bg.png) no-repeat center center;
    border: none;
}

/* Change the backgournd color of the Title on mouse hover*/
ul.thumb li .imageContainer .imageTitleHover
{
    background: #f0f0f0;
}

/* Resize the thumbnail image on mouse hover*/
ul.thumb li .imageContainer .imageHover
{
    width: 164px;
    height: 150px;
    background: #f0f0f0;
    -ms-interpolation-mode: bicubic;
    padding: 5px;
}

I also updated the jQuery code in order to animate the thumbnail on hover client side event.

$(document).ready(function() {
    // Larger thumbnail preview, hover intent 
    $("ul.thumb li").hover(function() {
        $(this).css({ 'z-index': '10' });
        $(this).find('.imageTitle').addClass("imageTitleHover");
        $(this).find('.imageContainer').find("img").addClass("imageHover");
        $(this).find('.imageContainer').addClass('hover').stop()
            .animate({
                    marginTop: '-110px',
                    marginLeft: '-110px',
                    top: '50%',
                    left: '50%',
                    width: '174px',
                    height: '174px',
                    padding: '20px'
                }, 200);

    }, function() {
        $(this).css({ 'z-index': '0' });
        $(this).find('.imageTitle').removeClass("imageTitleHover");
        $(this).find('.imageContainer').find("img").removeClass("imageHover");
        $(this).find('.imageContainer').removeClass('hover').stop()
            .animate({
                    marginTop: '0',
                    marginLeft: '0',
                    top: '0',
                    left: '0',
                    width: '100px',
                    height: '100px',
                    padding: '5px'
                }, 400);
    });
}); 

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0

About the Author

Akram El Assas

Architect
Mediatvcom
France France

Member

Follow on Twitter Follow on Twitter
"Every word, Every letter, Every number has an energetic equivalent. Thus, the verb becomes vibrations and this energetic message can flow in ambiant air through billions of small particles that allow networks to be constitued. Thanks to this, you have television." Bernard Woestelandt
 
"Human brain has plenty of decoders located between receptors (unconscious) and transmitters (concious).
 
The Vibrational messages, recieved in your brain (every idea that comes in your mind) are transformed into a comprehensible language. This language allows to communicate, to share, but the information is transformed because It goes through plenty of filters. That's why you're not obliged to take an information as a thruth. Check.
 
A mistaken information can flow from generation to generation and becomes thruth.
 
The Information transmited by human is called a language and let us be aware of its nature. It's only an information, It's up to us to check and modify our receptors - transmitters." Michelle-J. Noel
 


Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
Questioncan i use for my website Pinmemberductri23:50 30 Jan '12  
AnswerRe: can i use for my website PinmemberAkram El Assas1:42 31 Jan '12  
GeneralMy vote of 5 Pinmembertigercont22:41 28 Jan '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas12:26 29 Jan '12  
GeneralMy vote of 5 Pinmemberzabahey20:14 28 Jan '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas12:26 29 Jan '12  
GeneralMy vote of 5 Pinmemberguptasanjay5618:48 19 Jan '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas6:53 20 Jan '12  
GeneralMy vote of 5 PinmvpKanasz Robert2:04 18 Jan '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas11:19 19 Jan '12  
AnswerRe: My vote of 5 PinmvpKanasz Robert13:11 19 Jan '12  
GeneralMy vote of 5 Pinmemberhoernchenmeister2:55 16 Jan '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas8:10 16 Jan '12  
GeneralRe: My vote of 5 Pinmemberhoernchenmeister21:46 16 Jan '12  
GeneralMy vote of 5 PinmemberNagy Vilmos4:15 9 Jan '12  
GeneralRe: My vote of 5 PinmemberAkram El Assas7:38 9 Jan '12  
GeneralMy vote of 5 Pinmemberitaitai23:15 24 Dec '11  
GeneralRe: My vote of 5 PinmemberAkram El Assas12:52 4 Jan '12  
GeneralMy vote of 5 PinmemberTech Code Freak20:13 24 Dec '11  
GeneralRe: My vote of 5 PinmemberAkram El Assas12:52 4 Jan '12  
GeneralMy vote of 5 PinmemberRa-one19:03 21 Dec '11  
GeneralMy vote of 5 PinmemberMonjurul Habib17:57 18 Dec '11  
GeneralRe: My vote of 5 PinmemberAkram El Assas1:15 19 Dec '11  
GeneralMy vote of 5 PinmemberMary Jane Rutkowski7:57 8 Dec '11  
GeneralRe: My vote of 5 PinmemberAkram El Assas10:38 8 Dec '11  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web04 | 2.5.120222.1 | Last Updated 31 Jan 2012
Article Copyright 2011 by Akram El Assas
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid