Om Talsania's Geekypedia
Articles on .NET Architecture, Design, Development and Performance

Creating an Image Grid (Photo Gallery) with ASP.NET using ListView Control

Thursday, December 27, 2012 6:59 PM

 

ASP.NET has a good number of default controls which usually satisfy your needs. Especially the ListView control, which is a template based fully customizable control which can be tweaked to suit your needs. However, ASP.NET does not have a good Image Grid control (a kind of control you would want to use as a photo gallery), I will show you how you can create one simple Image Grid for yourself. I am using Visual Studio 2012 Express for Web for this exercise, however you can achieve the same with full version of Visual Studio 2012 or even with Visual Studio 2010.

The following is something we want to achieve

image

 

Step – 1 : Initial Setup

First create an empty ASP.NET Web Application. Now create one folder ‘Controls’. You don’t need to put your control in ‘Controls’ folder, however it is a good practice to put relevant things in its own folder.

image

Here you can see I have also create some other folders such as ‘Helpers’, ‘Images’ and Default.aspx (Remember? We chose empty ASP.NET web app, so Default. aspx will not be available by default).

Now, right click the folder and ‘Add’ –> ‘New Item’ –> ASP.NET Web User Control with name ‘ImageGrid.ascx’

image

 

Step-2 : Logger

As a habit, I always log exceptions so that later on the developer (I) will be able to determine if any error/exception occurred in production. For that purpose, I am going to write a simple logger which logs messages to Windows Event Log. You can write your own custom logger, or you may already have some good logging strategy in place (for example – ELMAH). If that is the case, then skip this step. Make sure you comment out Logger.Log(ex.Message) statements in the next sections, which uses the Logger class I am creating here.

Create a C# class named ‘Logger’ in Helpers folder.

using System;
using System.Diagnostics;

namespace SampleControls.Helpers
{
    /// <summary>
    /// A simple Logger which writes to Windoes Event Log
    /// </summary>
    public class Logger
    {
        static EventLog log;

        static Logger()
        {
            log = new EventLog("Application",Environment.MachineName, "Image Grid");
        }

        /// <summary>
        /// Logs the message into Windows Event Log.
        /// </summary>
        /// <param name="message">Error Message</param>
        public static void Log(string message)
        {
            log.WriteEntry(message, EventLogEntryType.Warning, 21001);
        }
    }
}

 

Step-3 : Image Grid MarkUp

Time to start creating the Image Grid. Open the ImageGrid.ascx you created in Step 1. In the markup page you will be adding a customized list view. Paste the following code in the markup.

<asp:ListView runat="server" ID="ImageListView" ItemPlaceholderID="itemPlaceHolder" 
     GroupPlaceholderID="groupPlaceHolder" OnItemCommand="ImageListView_ItemCommand">
    <LayoutTemplate>
        <h1>
            <asp:Label Text="" runat="server" ID="titleLabel" OnLoad="titleLabel_Load" />
        </h1>
        <div runat="server" id="groupPlaceHolder">
        </div>
    </LayoutTemplate>
    <GroupTemplate>
        <span>
            <div id="itemPlaceHolder" runat="server"></div>
        </span>
    </GroupTemplate>
    <ItemTemplate>
        <asp:ImageButton ID="itemImageButton" runat="server" 
          CommandArgument="<%# Container.DataItem %>" 
          ImageUrl="<%# Container.DataItem %>" Width="320" Height="240" 
          OnCommand="itemImageButton_Command"/>
        <asp:LinkButton ID="deleteLinkButton" runat="server" CommandName="Remove" 
          CommandArgument="<%# Container.DataItem %>" Text="Delete" Visible="false" 
          OnLoad="deleteLinkButton_Load"  />
</
ItemTemplate> <EmptyItemTemplate> <td /> </EmptyItemTemplate> <EmptyDataTemplate> <h3>No images available</h3> </EmptyDataTemplate> <InsertItemTemplate> <p> <asp:Label Text="Please upload an image" runat="server" ID="imageUploadLabel" /> <asp:FileUpload runat="server" ID="imageUpload" OnLoad="imageUpload_Load" /> <asp:Button ID="uploadButton" Text="Upload" runat="server" /> </p> <p> <asp:Label Text="" runat="server" ID="imageUploadStatusLabel" /> </p> </InsertItemTemplate> </asp:ListView>

This ListView is not bound to any DataSource yet, as we are going to bind it to a List<string> providing image path programmatically.

The most important piece of code lies in ItemTemplate and InsertItemTemplate. Inside ItemTemplate, we have 2 buttons. One ImageButton to display the image and a LinkButton to Delete the Image.The ImageButton binds to the current iterated item of the DataSource using Container.DataItem as ImageUrl. The same url is also used as CommandArgument which will be passed to the EventArgs while firing OnCommand, which is handled by itemImageButton_Command in order to show the Full Image.

The LinkButton is not Visible by default. It will be visible only when the AdminMode is enabled (We’ll soon cover how to set AdminMode). This check is handled in the Load event of the LinkButton.

Similarly the InsertItemTemplate is also visible only when the AdminMode is enabled.

In other words, If AdminMode is set then you can upload new Image as well as delete existing Images. If it is not set then all you see is the 320x240 icons of the Images in the ImageFolderPath (We’ll also cover how to set the ImageFolderPath in a moment). All can do is click on the image icon and it will display the FullImage.

 

Step-4 : Image Grid Code-Behind

Now it is time to write the Code-Behind.  Instead of pasting the full code-behind, let us take it in steps so that we can better understand it piece-by-piece.

    public partial class ImageGrid : System.Web.UI.UserControl
    {
private string virtualPath; private string physicalPath; /// <summary> /// Relative path to the Images Folder /// </summary> public string ImageFolderPath { get; set; } /// <summary> /// Title to be displayed on top of Images /// </summary> public string Title { get; set; } /// <summary> /// Get or Set the Admin Mode /// </summary> public bool AdminMode { get; set; }

We have 2 private members to store the virtual and actual path of the Image Folder, which can be set via the public property ImageFolderPath. Any public property of a User Control (ascx) can be set via attributes.

For Example, just as you would set <asp:TextBox Text=”something ….. you would use <cc:MyCustomControlOrWhatever MyProperty=”” ……. So when you will actually use this grid in an ASPX page, it will be very simple to set any folder containing images in ImageFolderPath attribute and whether you want to display the AdminMode controls (upload and delete functionality). If you want to control AdminMode programmatically by identifying whether the user has access or not, it is just as easy, because ultimately it is just a public property of the ImageGrid class.

        /// <summary>
        /// Page load operations
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void Page_Load(object sender, EventArgs e)
        {
            //Update the path
            UpdatePath();

            //Show AdminMode specific controls
            if (AdminMode)
            {
                ImageListView.InsertItemPosition = InsertItemPosition.FirstItem;
            }

        }

        /// <summary>
        /// Pre render operations
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void Page_PreRender(object sender, EventArgs e)
        {
            //Binds the Data Before Rendering
            BindData();
        }

When the ImageGrid loads, it will first update the virtual and physical path by using the values inside ImageFolderPath. If AdminMode is enabled, it will enable the InsertItemTemplate. Before actual rendering, the data binding will happen and the ImageGrid will be updated with current Images. UpdatePath() and BindData() are shown in the snippet below.

        /// <summary>
        /// Updates the path variables
        /// </summary>
        private void UpdatePath()
        {
            //use a default path
            virtualPath = "~/Images";
            physicalPath = Server.MapPath(virtualPath);

            //If ImageFolderPath is specified then use that path
            if (!string.IsNullOrEmpty(ImageFolderPath))
            {
                physicalPath = Server.MapPath(ImageFolderPath);
                virtualPath = ImageFolderPath;
            }

        }

        /// <summary>
        /// Binds the ImageListView to current DataSource
        /// </summary>
        private void BindData()
        {
            ImageListView.DataSource = GetListOfImages();
            ImageListView.DataBind();

        }

        /// <summary>
        /// Gets list of images
        /// </summary>
        /// <returns></returns>
        private List<string> GetListOfImages()
        {
            var images = new List<string>();

            try
            {
                var imagesFolder = new DirectoryInfo(physicalPath);
                foreach (var item in imagesFolder.EnumerateFiles())
                {
                    if (item is FileInfo)
                    {
                        //add virtual path of the image to the images list
                        images.Add(string.Format("{0}/{1}", virtualPath, item.Name));
                    }
                }
            }
            catch (Exception ex)
            {
                //log exception
                Logger.Log(ex.Message);
            }

            return images;

        }

The above snippet is self-explanatory. Just note one thing that the code takes the default path as “~/Images”. In other words, if you don’t specify ImageFolderPath, then by default the code will pick up Images inside ‘Images’ folder. Another good practice would be to put all the images in such a separate folder.

Now let’s walk through some other event handlers.

        /// <summary>
        /// Sets Title
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void titleLabel_Load(object sender, EventArgs e)
        {
            var titleLabel = sender as Label;
            if (titleLabel == null) return;

            titleLabel.Text = Title;
        }
        /// <summary>
        /// Enables delete functionality for Admin Mode
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void deleteLinkButton_Load(object sender, EventArgs e)
        {
            //In case of AdminMode, we would want to show the delete button 
            //which is not visible by iteself for Non-Admin users
            if (AdminMode)
            {
                var deleteButton = sender as LinkButton;
                if (deleteButton == null) return;

                deleteButton.Visible = true;
            }

        }
        /// <summary>
        /// Redirects to the full image when the image is clicked
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void itemImageButton_Command(object sender, CommandEventArgs e)
        {
            Response.Redirect(e.CommandArgument as string);
        }

When the titleLabel control is loaded, it picks text provided via the Title property. Another customizable thing.

As explained previously, the deleteLinkButton gets visible on-the-fly if AdminMode is enabled.

When an image is clicked, the Url is passed via CommandArgument, which is then used to Redirect.

        /// <summary>
        /// Performs commands for bound buttons in the ImageListView. In this case 
        /// 'Remove (Delete)'
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void ImageListView_ItemCommand(object sender, ListViewCommandEventArgs e)
        {
            /* We have not bound the control to any DataSource derived controls, 
            nor do we use any key to identify the image. Hence, it makes more sense not to 
            use 'delete' but to use a custom command 'Remove' which can be fired as a 
            generic ItemCommand, and the ListViewCommandEventArgs e will have 
            the CommandArgument passed by the 'Remove' button In this case, it is the bound 
            ImageUrl that we are passing, and making use it of to delete the image.*/
            switch (e.CommandName)
            {
                case "Remove":
                    var path = e.CommandArgument as string;
                    if (path != null)
                    {
                        try
                        {
                            FileInfo fi = new FileInfo(Server.MapPath(path));
                            fi.Delete();

                            //Display message
                            Parent.Controls.Add(new Label() { 
                                Text = GetFileName(path) + " deleted successfully!" 
                            });

                        }
                        catch (Exception ex)
                        {
                            Logger.Log(ex.Message);
                        }
                    }
                    break;
                default:
                    break;
            }
        }

The delete operation is performed by the above snippet. Here, instead of ‘Delete’ we have passed a custom ‘Remove’ command, which seems odd. This is done order to utilize the CommandArgument which is not available had we used the ‘Delete’ command.

        /// <summary>
        /// Saves the Posted File
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void imageUpload_Load(object sender, EventArgs e)
        {
            //Get the required controls
            var imageUpload = sender as FileUpload;
            if (imageUpload == null) return;

            var parent = imageUpload.Parent;
            if (parent == null) return;

            var imageUploadStatus = parent.FindControl("imageUploadStatusLabel") as Label;
            if (imageUploadStatus == null) return;


            //If a file is posted, save it
            if (this.IsPostBack)
            {
                if(imageUpload.PostedFile != null && imageUpload.PostedFile.ContentLength>0)
                {
                    try
                    {
                        imageUpload.PostedFile.SaveAs(string.Format("{0}\\{1}", 
                            physicalPath, GetFileName(imageUpload.PostedFile.FileName)));
                        imageUploadStatus.Text = string.Format(
                            "Image {0} successfully uploaded!", 
                            imageUpload.PostedFile.FileName);
                    }
                    catch (Exception ex)
                    {
                        Logger.Log(ex.Message);
                        imageUploadStatus.Text = string.Format("Error uploading {0}!", 
                            imageUpload.PostedFile.FileName);
                    }
                }
                else
                {
                    imageUploadStatus.Text = string.Empty;
                }

            }

        }

When the FileUpload control is loaded and IsPostBack is true, it checks for the PostedFile property which contains uploaded image. The Image is saved in the physical path which was set initially.

        /// <summary>
        /// Get File Name
        /// </summary>
        /// <param name="path">full path</param>
        /// <returns>string containing the file name</returns>
        private string GetFileName(string path)
        {
            DateTime timestamp = DateTime.Now;
            string fileName = string.Empty;
            try
            {
                if (path.Contains('\\')) fileName = path.Split('\\').Last();
                if (path.Contains('/')) fileName = path.Split('/').Last();
            }
            catch (Exception ex)
            {
                Logger.Log(ex.Message);
            }
            return fileName;
        }

The above snippet show the GetFileName helper method used in some of the handlers.

 

Whew! Finally… it is time to use the control !!!

 

Step-5 : ImageGrid in Action

All this lengthy and confusing work will finally pay off Smile You just need to add the following code in your page. We had added a Default.aspx. Paste the following markup in there.

Just below the page directive:

<%@ Register TagPrefix="cc" TagName="ImageControl" Src="~/Controls/ImageGrid.ascx" %>

Inside the body (or Content control if you have been using MasterPages):

        <cc:ImageControl runat="server" ID="MyImageGrid"  />

Now, put some images inside the Image folder and hit F5. I have kept some of my personal photos in the Images folder and here is the output:

image

 

If you want to specify a different path and give a title, just use the following snippet instead:

<cc:ImageControl runat="server" ID="MyImageGrid" ImageFolderPath="~/Images/Wallpapers" 
                 Title="C# Wallpapers" AdminMode="true" />

Now I have specified my C# wallpapers path. Additionally, I have also specified AdminMode=”true”. With that in place, the result would look like the following:

image

Now you can see the Title, Image Upload control, as well as Delete Links.

 

I believe you would want to control AdminMode programmatically, like the following snippet:

    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if(User.IsInRole("Administrator")) MyImageGrid.AdminMode = true;
        }

    }

Before using this last piece, however, make sure you have setup Roles and the current user accessing the page is in ‘Administrator’ role. Of course, Membership and Roles are totally different topics and out of the scope of this tutorial.


Download the Source Code

You can download the source code from the following location:

download_button_2_mod


 

Well, I hope you have enjoyed this tutorial. If so, please do comment.

Thank You!

 




Feedback

# re: Creating an Image Grid (Photo Gallery) with ASP.NET using ListView Control

Nice Work.. 3/7/2013 4:49 PM | Shrikant

# different size for every photo

what if you had different sizes of images, let say we give them the same width, that would give a different height each time , is that doable? 5/9/2013 2:35 AM | theFuquan

# re: Creating an Image Grid (Photo Gallery) with ASP.NET using ListView Control

Hi Sir,

I have implemented your concept and it works like magic. Sir, please help me in associating informations with each image like the total views and total downloads displaying for each image. I know that we need to make a datatable having these fields , but somehow I am not able to implement it. Please help me sir.... Thanks in advance.... 5/12/2013 6:56 PM | Mayur

# re: Creating an Image Grid (Photo Gallery) with ASP.NET using ListView Control

@theFuquan: Yes I believe that should work.

@Mayur: I'm glad it is working for you :)

In that case, basically you will have to maintain a database where you store the total hit count for each image. This can be handled in itemImageButton_Command handler. Before you redirect the response to target image, you can increase the value of counter in the database. ImagePath can serve as primary key in database table.

Another change is to be made in ItemTemplate, where you can have a new label below the image. You can bind that label to the counter from database during OnLoad event. 5/13/2013 12:07 PM | Om Talsania

# re: Creating an Image Grid (Photo Gallery) with ASP.NET using ListView Control

A nice quick photo gallery, thank you.
5/21/2013 7:10 AM | James

# re: Creating an Image Grid (Photo Gallery) with ASP.NET using ListView Control

I don't suppose you have some pointers for adding a slide show to this? 5/21/2013 11:31 PM | James

# re: Creating an Image Grid (Photo Gallery) with ASP.NET using ListView Control

I've done everything , when i run the page nothing works ??

any idea why ?

also is there any way to get the code in vb.net 6/1/2013 7:28 AM | Ghrawi

# re: Creating an Image Grid (Photo Gallery) with ASP.NET using ListView Control

hi, i have tried but its working.... can u upload the project files..... from that we can download..... 6/2/2013 5:42 PM | SSS

# re: Creating an Image Grid (Photo Gallery) with ASP.NET using ListView Control

hi, i have tried but its not working not displaying the images.... can u upload the project files..... from that we can download..... 6/2/2013 5:44 PM | sss

# re: Creating an Image Grid (Photo Gallery) with ASP.NET using ListView Control

Hello,
It's failing to upload. It gives error that system cannot find the path. Is it some access issue?

Thanks,
Manu 6/4/2013 11:41 AM | manu

# re: Creating an Image Grid (Photo Gallery) with ASP.NET using ListView Control

i need to display it with master page,but we can nest master page with web form only...will it possible with Default.aspx 9/30/2013 7:56 PM | asok

# re: Creating an Image Grid (Photo Gallery) with ASP.NET using ListView Control

Hi Friend.

I have implemented your concept and made a few tweaks to get it to pull the images from a folder on my server... This all works fine on IE9 but when I use it with IE10, the images don't display... How can I sort out this problem? 10/25/2013 4:45 PM | Ryan Pillay

# re: Creating an Image Grid (Photo Gallery) with ASP.NET using ListView Control

@Ryan Pillay: Hi, thanks for the comment. I have tested the code and it works perfectly fine in IE10. I have edited the post and uploaded the sample files. Please compare it with your code. It could be something that you might have changed. 10/27/2013 2:38 PM | Om Talsania

# re: Creating an Image Grid (Photo Gallery) with ASP.NET using ListView Control

Hi Folks, thanks for your comments. I have uploaded Sample project.

@ghrawi: Please try out the sample from the download link. It should help you out.
@manu: The path that you have provided should exist otherwise it will give error. Please try out the sample from the download link.
@asok: I am not if I understand what you are trying to achieve. The technique I have provided works well with aspx page (Default.aspx as in my Sample) and you can modify it as per your needs. Please try out the sample from the download link.
@sss: Please check the post again. I have uploaded the Sample files. 10/27/2013 2:42 PM | Om Talsania

# re: Creating an Image Grid (Photo Gallery) with ASP.NET using ListView Control

Hi, is there any way you could tweak your example to show how to pull the images from a folder on your desktop rather than the images folder within the system 10/28/2013 3:53 PM | Ryan Pillay

# re: Creating an Image Grid (Photo Gallery) with ASP.NET using ListView Control

Hi, I am Also having the same problem as Ryan ......is there any way you could tweak your example to show how to pull the images from a folder on your desktop rather than the images folder within the system as I want to display images from a server 11/20/2013 4:42 PM | Sunil Singh

# re: Creating an Image Grid (Photo Gallery) with ASP.NET using ListView Control

Hi Ryan and Sunil,

If you keep your images inside any subfolder under the application root (where you have deployed this application), you can simply add an attribute while using this control. For Example:
ImageFolderPath="~/MyFolder/MySubfolder/FolderWhereIHaveKeptMyImages"

The Images are bound using virtual path, because that's how the links work, i.e. when you click on image the actual image is shown. If you keep your Images on Desktop, there is no way the web application deployed over IIS would be able to relate to that path. There is a hack to convert physical path into relative path, however, it doesn't work in such scenarios. 12/5/2013 12:59 AM | Om Talsania

# re: Creating an Image Grid (Photo Gallery) with ASP.NET using ListView Control

Where would I add the code snippet to control AdminMode programmatically. What file does it go in?
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if(User.IsInRole("Administrator")) MyImageGrid.AdminMode = true;
}

} 2/20/2014 10:35 AM | Feis Mom

Post a comment