Useful C# Image Upload Example

Share |
by Rob 21. September 2010 14:38

For one of the websites I have created I needed my users to be able to upload images to the server and create multiple versions of these images in different sizes. This is useful if you need thumbnail and full-size images, etc.

I’ll show you how I approached the problem and if you think you will find the code useful then you can download it here.

Requirements

  • Image dimensions should be in one place so they can be easily changed if necessary.
  • Uploader should only allow images of the types PNG, GIF and JPG.
  • If upload directory does not exist it should be created automatically.
  • Upload page should show currently uploaded images and update this list when new images are added or deleted.

The Code

To start with I created a simple wrapper for the image information I will want to store in a BusinessObject project called Media.

Media.cs

using System;

namespace BusinessObject
{
    public class Media
    {
        public Guid MediaId { get; set; }
        public string FolderName { get; set; }
        public string URL { get; set; }
    }
}

I then set up a GlobalConstants class in a Common project. This class contains constants that are used throughout the code. It is in this class that we define the different image dimensions for the images we want to create on a user upload.

GlobalConstants.cs

using System.Collections.Generic;
using System.Linq;

namespace Common
{
    public class GlobalConstants
    {
        public class Imaging
        {
            public const int ThumbnailDim = 100;
            public const int ProfileDim = 250;
            public const int StandardDim = 450;

            public const string SaveFolderName = "MyImages";
            public const string SaveFolderNameServerPath = "Storage\\" +  SaveFolderName;

            public static string ThumbnailUrlPrefix = GetImageUrlPrefix(ThumbnailDim);
            public static string ProfileUrlPrefix = GetImageUrlPrefix(ProfileDim);
            public static string StandardUrlPrefix = GetImageUrlPrefix(StandardDim);

            public static string ThumbnailServerUrlPrefix = 
                GetImageServerUrlPrefix(ThumbnailDim);
            public static string ProfileServerUrlPrefix = GetImageServerUrlPrefix(ProfileDim);
            public static string StandardServerUrlPrefix = 
                GetImageServerUrlPrefix(StandardDim);

            public static List<int> ImageDims = new List<int>
                                                    {
                                                        ThumbnailDim, 
                                                        ProfileDim, 
                                                        StandardDim
                                                    };

            public static List<string> ImageServerUrlPrefixes = 
                GetImageServerUrlPrefixes_List();
            public static List<string> ImageUrlPrefixes = GetImageUrlPrefixes_List();
        }

        private static string GetImageUrlPrefix(int dim)
        {
            return "/" + dim + "x" + dim + "_";
        }

        private static List<string> GetImageUrlPrefixes_List()
        {
            return Imaging.ImageDims.Select(GetImageUrlPrefix).ToList();
        }

        private static string GetImageServerUrlPrefix(int dim)
        {
            return "\\" + dim + "x" + dim + "_";
        }

        private static List<string> GetImageServerUrlPrefixes_List()
        {
            return Imaging.ImageDims.Select(GetImageServerUrlPrefix).ToList();
        }
    }
}

I have called the different Image dimensions ThumbnailDim, ProfileDim and StandardDim but you should pick a naming convention for each image dimension that makes sense to you. I have added some extra fields for ease of use (…UrlPrefix fields and …ServerUrlPrefix fields) which will need altering if any dimensions are added or deleted from the list. It is of course possible to not use these and simply reference the method they are implementing instead, but I find having them there useful.

The image uploader will keep the aspect ratio of the oroginal image and then resize it so it’s longest side is the same length as the image dimensions above. (So for example a 1000x500 original would allow us to create a 100x50, a 250x125 and a 450x225 images).

A prefix will be added to each uploaded image that is saved on the server so that each group of differently saved files can have the same filename. For example, a file named picture.jpg would be saved 3 times as 100x100_picture.jpg, 250x250_picture.jpg and 450x450_picture.jpg. This is great when using a database to reference the files on the server, as you can simply save a record on the database as picture.jpg, and then reference any sized instance of that picture by adding the appropriate prefix.

Default.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" 
Inherits="ImageUploader.Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Test image uploader</title>
</head>
<body>
    <form id="form1" runat="server">
    <div id="uploadarea">
        <asp:Literal ID="uploadMsg" runat="server"></asp:Literal>
        <div id="Div1" runat="server">
            <input type="file" size="45" runat="server" id="FileUpload1" />
            <asp:Label ID="Label1" runat="server" />
        </div>
        <div id="Div2" runat="server">
            <input type="file" size="45" runat="server" id="FileUpload2" />
            <asp:Label ID="Label2" runat="server" />
        </div>
        <div id="Div3" runat="server">
            <input type="file" size="45" runat="server" id="FileUpload3" />
            <asp:Label ID="Label3" runat="server" />
        </div>
        <br />
        <span>
        
        <asp:Button ID="btnSubmit" runat="server" OnClick="btnSubmit_Click" 
            Text="Upload Images" /></span>
    </div>
    
    <p></p>
    <p></p>
    
    <asp:Repeater ID="PhotoRepeater" runat="server">
          <ItemTemplate>
              <div>
                <img src="/Storage/<%#DataBinder.Eval(Container.DataItem, "FolderName")%>
                /100x100_<%#DataBinder.Eval(Container.DataItem, "URL")%>" 
                    alt='<%#DataBinder.Eval(Container.DataItem, "URL")%>' />
                  <asp:LinkButton ID="DeleteImageBtn" runat="server" Text="Delete" 
                    CommandName="mediaId" OnCommand="DeleteImageBtn_Click" 
                    CommandArgument='<%#DataBinder.Eval(Container.DataItem, "MediaId")%>'>
                    </asp:LinkButton>
              </div>
          </ItemTemplate>
      </asp:Repeater>
    </form>
</body>
</html>

 

Default.aspx.cs

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using BusinessObject;
using Common;
using Image = System.Drawing.Image;

namespace ImageUploader
{
    public partial class Default : Page
    {
        public List<Media> ImageList = new List<Media>();

        /// <summary>
        /// Handles the Load event of the Page control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event 
        /// data.</param>
        protected void Page_Load(object sender, EventArgs e)
        {
            if (Page.IsPostBack) return;

            // on page load get any images that are in the test folder directory and bind 
            // them to the repeater.
            GetImagesCurrentlyInTestFolder();
        }

        /// <summary>
        /// Handles the Click event of the btnSubmit control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event 
        /// data.</param>
        protected void btnSubmit_Click(object sender, EventArgs e)
        {
            // gets an HttpFileCollection of all the files that have been uploaded
            var uploadFilCol = Request.Files;

            // so now we need to iterate through the list of files and deal with each one in 
            // turn
            for (var i = 0; i < uploadFilCol.Count; i++)
            {
                // chuck the current file in the loop in a variable called file
                var file = uploadFilCol[i];

                // call CheckFileIsAnImage to make sure some muppet hasn't tried to upload a 
                // txt file or something
                var msg = CheckFileIsAnImage(file);

                // if msg is empty we know our file is an ok image, if not we need to show a 
                // message to the user
                if (!string.IsNullOrEmpty(msg))
                    ShowMessage(msg, i);
                else
                {
                    // our file is ok. so now we get the filename of the file we are looking at
                    var fileName = Path.GetFileName(file.FileName);

                    // then we call CheckAndCreateDirectory which sees if the directory 
                    // structure we want to save to exists.
                    // if it doesn't exist it creates the directory for us.
                    // either way, the method will return the path to save the image to.
                    //
                    // this is where you would do some work on creating a custom folder root 
                    // if that's what you wanted (for example if you wanted another folder 
                    // within the storage system that was the artists name, or a gallery
                    // id, etc, etc.
                    var savePath = CheckAndCreateDirectory(
                           Server.MapPath(GlobalConstants.Imaging.SaveFolderNameServerPath));

                    // now we save the image to the server. in the process we allocate a new 
                    // filename (in this case a guid).
                    // you can use whatever you like here but remember all image names must 
                    // be unique.
                    var newFilename = SaveUploadedImageToServer(savePath, file);

                    // here is where you would save the newFilename to the db

                    // we've got this far so we can tell the user this file was saved ok
                    ShowMessage(" " + fileName + " uploaded successfully", i);
                }
            }

            // refresh the repeater with all images in the test folder
            GetImagesCurrentlyInTestFolder();
        }

        /// <summary>
        /// Checks the file is an image.
        /// </summary>
        /// <param name="file">The file.</param>
        /// <returns></returns>
        public static string CheckFileIsAnImage(HttpPostedFile file)
        {
            // if the file is null or has no content it's obviously a dirty wrongun
            if (file == null) return "file is invalid, upload must have failed";
            if (file.ContentLength == 0) return "no file specified";

            // get the filename extension (e.g .jpg, .gif, etc) from the filename
            var extension = Path.GetExtension(file.FileName).ToLower();

            // we're only supporting jpg, png and gif in this (you can support more if you 
            // like though)
            if (extension != ".jpg" && extension != ".gif" && extension != ".png")
                return "unsupported image type";

            // if we got this far the file looks good, so return an empty string (which 
            // means everything ok)
            return string.Empty;
        }

        /// <summary>
        /// Checks the directory exists and creates if not.
        /// </summary>
        /// <param name="directory">The directory.</param>
        /// <returns></returns>
        public static string CheckAndCreateDirectory(string directory)
        {
            if (!Directory.Exists(directory))
                Directory.CreateDirectory(directory);

            return directory;
        }

        /// <summary>
        /// Shows the file upload message.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="fileUploadPos">The file upload pos.</param>
        private void ShowMessage(string message, int fileUploadPos)
        {
            // show messages for each file being uploaded (3 files, so have to have a 
            // message for each)
            switch (fileUploadPos)
            {
                case 0:
                    Label1.Text = message;
                    break;
                case 1:
                    Label2.Text = message;
                    break;
                default:
                    Label3.Text = message;
                    break;
            }
        }

        /// <summary>
        /// Saves the uploaded image to the server.
        /// </summary>
        /// <param name="filePath">The file path.</param>
        /// <param name="file">The file.</param>
        /// <returns></returns>
        public string SaveUploadedImageToServer(String filePath, HttpPostedFile file)
        {
            // first thing to do is create a new filename root by using a guid and the 
            // current file extension.
            // i use the guid as the filename and as the unique media id in the database, 
            // but you don't have to do it this way
            var mediaId = Guid.NewGuid();
            var fileNameExtension = Path.GetExtension(file.FileName).ToLower();

            // now we iterate through our image dimensions list so we can create the image 
            // sizes we want from the image uploaded.
            // we are creating 3 images - 100x100, 250x250 and 450x450
            foreach (var imageDim in GlobalConstants.Imaging.ImageDims)
            {
                // get the file as an image
                var uploadedImage = Image.FromStream(file.InputStream);
                // create our new filename using the media id, file extension and image 
                // dimensions.
                var fileName = imageDim + "x" + imageDim + "_" + mediaId + fileNameExtension;
                // now get the full server path and add on the filename
                var fullSavePath = filePath + "\\" + fileName;
                // we need to get the right ImageFormat so we don't try to save a jpg as a 
                // png, etc...
                var imageType = GetImageType(fileNameExtension);

                // if the height and width of the uploaded image are less that the size we 
                // are aiming for (e.g we want 100x100, they uploaded 80x80), just save the 
                // image as is.
                if (uploadedImage.Height <= imageDim && uploadedImage.Width <= imageDim)
                {
                    // save the image
                    uploadedImage.Save(fullSavePath, imageType);
                    // then get rid of it from memory
                    uploadedImage.Dispose();
                }
                else
                {
                    var newWidth = imageDim;
                    var newHeight = imageDim;

                    // check the heigh and width ratio. if we are making a 100x100 image, 
                    // we may need to make it 70x100 or 100x70 depending
                    // whether the image is landscape or portrait
                    if (uploadedImage.Height > uploadedImage.Width)
                        newWidth = (Int32) Math.Ceiling(
                            ((double) uploadedImage.Width/uploadedImage.Height)*imageDim);
                    else
                        newHeight = (Int32) Math.Ceiling(
                            ((double) uploadedImage.Height/uploadedImage.Width)*imageDim);

                    // now write a new image out using the dimensions we've calculated
                    var smallerImg = new Bitmap(newWidth, newHeight);
                    var g = Graphics.FromImage(smallerImg);

                    // a few options, no need to worry about these
                    g.SmoothingMode = SmoothingMode.HighQuality;
                    g.InterpolationMode = InterpolationMode.HighQualityBicubic;
                    g.PixelOffsetMode = PixelOffsetMode.HighQuality;

                    foreach (var pItem in uploadedImage.PropertyItems)
                    {
                        smallerImg.SetPropertyItem(pItem);
                    }

                    // draw the image
                    g.DrawImage(uploadedImage, new Rectangle(0, 0, newWidth, newHeight));

                    // get rid of the uploaded image stream from memory
                    uploadedImage.Dispose();

                    // save the new file
                    smallerImg.Save(fullSavePath, imageType);

                    // get rid of the new file from memory
                    smallerImg.Dispose();
                }
            }

            // return the new filename
            return mediaId + fileNameExtension;
        }

        /// <summary>
        /// Gets the type of the image.
        /// </summary>
        /// <param name="fileExt">The file ext.</param>
        /// <returns></returns>
        private static ImageFormat GetImageType(string fileExt)
        {
            switch (fileExt)
            {
                case ".jpg":
                    return ImageFormat.Jpeg;
                case ".gif":
                    return ImageFormat.Gif;
                default: // (png)
                    return ImageFormat.Png;
            }
        }

        /// <summary>
        /// Handles the Click event of the DeleteImageBtn control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Web.UI.WebControls.CommandEventArgs"/> 
        /// instance containing the event data.</param>
        public void DeleteImageBtn_Click(Object sender, CommandEventArgs e)
        {
            DeleteImageFromDb(e);

            DeleteImageFromServer(e);

            GetImagesCurrentlyInTestFolder();
        }

        /// <summary>
        /// Deletes the image from server.
        /// </summary>
        /// <param name="e">The <see cref="System.Web.UI.WebControls.CommandEventArgs"/> 
        /// instance containing the event data.</param>
        private void DeleteImageFromServer(CommandEventArgs e)
        {
            DeleteImage(Server.MapPath(GlobalConstants.Imaging.SaveFolderNameServerPath), 
                e.CommandArgument.ToString());
        }

        /// <summary>
        /// Deletes the image.
        /// </summary>
        /// <param name="directoryPath">The directory path.</param>
        /// <param name="mediaId">The media id.</param>
        public static void DeleteImage(String directoryPath, String mediaId)
        {
            DeleteImagesInList(Directory.GetFiles(directoryPath, "*" + mediaId + "*"));
        }

        /// <summary>
        /// Deletes the images in list.
        /// </summary>
        /// <param name="imgList">The img list.</param>
        private static void DeleteImagesInList(IEnumerable<string> imgList)
        {
            foreach (var img in imgList)
            {
                var imgInfo = new FileInfo(img);
                imgInfo.Delete();
            }
        }

        /// <summary>
        /// Deletes the image from db.
        /// </summary>
        /// <param name="e">The <see cref="System.Web.UI.WebControls.CommandEventArgs"/> 
        /// instance containing the event data.</param>
        private void DeleteImageFromDb(CommandEventArgs e)
        {
            // here you would remove all references to the particular image in the db
        }

        /// <summary>
        /// Gets the images currently in test folder and bind to the photo repeater.
        /// </summary>
        private void GetImagesCurrentlyInTestFolder()
        {
            GetImages();

            PhotoRepeater.Dispose();
            PhotoRepeater.DataSource = ImageList;
            PhotoRepeater.DataBind();
        }

        /// <summary>
        /// Gets the images from the server location.
        /// </summary>
        public void GetImages()
        {
            // if the directory is there we'll have to create it or it'll fall over on the 
            // reference below
            var savePath = CheckAndCreateDirectory(
                Server.MapPath(GlobalConstants.Imaging.SaveFolderNameServerPath));

            // get all the files in the test directory
            var allFiles = Directory.GetFiles(savePath, "*");

            // for every file in the directory create a media object and add it to our list 
            // so we can bind it to the repeater
            foreach (var file in allFiles)
            {
                // get the filename
                var media = new Media {FolderName = GlobalConstants.Imaging.SaveFolderName};

                var splitterArrayToLoseAllFoldersInPath = file.Split(new[] {'\\'});
                var lastIndex = splitterArrayToLoseAllFoldersInPath.Length - 1;
                var getTheFileNameWhichIsTheLastItemInTheArray =
                    splitterArrayToLoseAllFoldersInPath[lastIndex];

                // we only want the thumbnail images
                if (!getTheFileNameWhichIsTheLastItemInTheArray.StartsWith(
                    GlobalConstants.Imaging.ThumbnailDim + "x" +
                    GlobalConstants.Imaging.ThumbnailDim)) 
                    continue;

                var filenameWithoutDimensions = 
                    getTheFileNameWhichIsTheLastItemInTheArray.Substring(8);

                media.URL = filenameWithoutDimensions;

                var splitterArrayToLoseFileExtension = 
                    filenameWithoutDimensions.Split(new[] {'.'});

                media.MediaId = new Guid(splitterArrayToLoseFileExtension[0]);

                ImageList.Add(media);
            }
        }
    }
}

The above is the code behind for our image uploader page. The code is heavily commented, so hopefully there shouldn’t be any problems understanding what it does. The code was adapted from the original code I wrote which saved the image details for all the uploaded images to a database. I have left a couple of references to the database in the code so you can understand where to implement this functionality when it is required. For now, the code has been adapted to just use the file system instead.

I’m not going to go through the code and explain it line by line, instead I would rather you downloaded the solution and have a play around with it. It should be self-explanatory – especially if you run through in debug mode.

Enjoy :o)

Tags:

Share |

Contact   |   Archive   |   Log in

Powered by BlogEngine.NET 1.6.1.0