ASP.NET MVC – Image Controller & Thumbnail Action – Resize Images On The Fly

Download The Code
If you need to resize an image or create a thumbnail on the fly there are number of ways to do so in ASP.NET MVC. After a quick scout around on Google I decided to create some reusable code.  I needed three main classes to get this up and running - ImageController, ThumbnailResult and some html helpers to generate the mark-up for the view.

First I created the ThumbnailResult class which is derived from ActionResult. This is where all the work is carried out to create the thumbnail / resized image. I decided to force a convention here and state that all images should reside in the Content\Images folder of the web application.

using System;
using System.Web.Mvc;
using System.Drawing.Imaging;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
namespace MVC.Image.Resize.Helpers
{
    public class ThumbnailResult : ActionResult
    {
        public int Width { get; set; }
        public int Height { get; set; }
        public string Filename { get; set; }

        public ThumbnailResult(int Width, int Height, string Filename)
        {
            this.Width = Width;
            this.Height = Height;
            this.Filename = Filename;
        }

        public override void ExecuteResult(ControllerContext context)
        {
            if (string.IsNullOrEmpty(Filename))
                 throw new NullReferenceException("parameter Image cannot be null or empty");

            //A little convention - place all images in the path Content/Images
            string FilePath = context.HttpContext.Server.MapPath(context.HttpContext.Request.ApplicationPath) 
            + @"Content\Images\" + Filename;

            if(!File.Exists(FilePath))
                throw new FileNotFoundException("Image does not exist at " + FilePath);

            Bitmap bitmap = new Bitmap(FilePath);
            
            try
            {
                if (bitmap.Width < Width && bitmap.Height < Height)
                {
                    context.HttpContext.Response.ContentType = "image/gif";
                    bitmap.Save(context.HttpContext.Response.OutputStream, ImageFormat.Jpeg);
                    return;
                }
            }
            catch (Exception e)
            {
                throw new Exception(e.Message);
            }
            finally
            {
                bitmap.Dispose();
            }

            Bitmap FinalBitmap = null;

            try
            {
                bitmap = new Bitmap(FilePath);

                int BitmapNewWidth;
                decimal Ratio;
                int BitmapNewHeight;

                if (bitmap.Width > bitmap.Height)
                {
                    Ratio = (decimal)Width / bitmap.Height;
                    BitmapNewWidth = Width;

                    decimal temp = bitmap.Height * Ratio;
                    BitmapNewHeight = (int)temp;
                }
                else
                {
                    Ratio = (decimal)Height / bitmap.Height;
                    BitmapNewHeight = Height;
                    decimal temp = bitmap.Width * Ratio;
                    BitmapNewWidth = (int)temp;
                }

                FinalBitmap = new Bitmap(BitmapNewWidth, BitmapNewHeight);
                Graphics graphics = Graphics.FromImage(FinalBitmap);
                graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
                graphics.FillRectangle(Brushes.White, 0, 0, BitmapNewWidth, BitmapNewHeight);
                graphics.DrawImage(bitmap, 0, 0, BitmapNewWidth, BitmapNewHeight);

                context.HttpContext.Response.ContentType = "image/gif";
                FinalBitmap.Save(context.HttpContext.Response.OutputStream, ImageFormat.Jpeg);

            }
            catch (Exception e)
            {
               throw new Exception(e.Message);
            }
            finally
            {
                if (FinalBitmap != null) FinalBitmap.Dispose();
            }
           
        }
    }

I then created the ImageController that is derived from Controller. The ImageController exposes the ThumbnailResult action. When creating a controller that serves resized images / thumbnails you must make sure the controller derives from ImageController rather than Controller.
 
using System.Web.Mvc;

namespace MVC.Image.Resize.Helpers
{
    public class ImageController : Controller
    {
        protected internal virtual ThumbnailResult Thumbnail(int Width, int Height, string Filename)
        {
            return new ThumbnailResult(Width, Height, Filename);
        }
    }
}

I created some html helpers to facilitate the creation of mark-up for a thumbnail / resized image in the view. (Please note: there is probably a better way to do this!!! If anyone can suggest anything please e-mail me dan@dotnetguy.co.uk).

namespace MVC.Image.Resize.Helpers
{
    public static class ImageHelper
    {
        //0 - Image Source
        //1 - Alt Tag
        //2 - Styles
        //3 - Class
        const string ImageTag = "<img src=\"{0}\" alt=\"{1}\" {2} {3} />";

        public static string Thumbnail(this HtmlHelper Helper,
                         string Controllername,
                         string Action,
                         int Width,
                         int Height,
                         string File)
        {
            string Imagelocation = ThumbnailLocation(Helper,Controllername, Action, Width, Height, File);
            
            //WC3 - ALT tag always needed so when not set then set to the filename.
            return string.Format(ImageTag, Imagelocation, File, null, null);
        }

        public static string Thumbnail(this HtmlHelper Helper, 
                                        string Controllername, 
                                        string Action, 
                                        int Width, 
                                        int Height, 
                                        string File,
                                        string Alt)
        {
            string Imagelocation = ThumbnailLocation(Helper, Controllername, Action, Width, Height, File);

            return string.Format(ImageTag, Imagelocation, Alt, null, null);
        }

        public static string Thumbnail(this HtmlHelper Helper,
                                string Controllername,
                                string Action,
                                int Width,
                                int Height,
                                string File,
                                string Alt,
                                IDictionary<string, object> styleAttributes)
        {
            string Imagelocation = string.Format("/{0}/{1}/{2}/{3}/{4}", Controllername, Action, Width, Height, File);

            StringBuilder builder = new StringBuilder();
            string Class = string.Empty;

            if (styleAttributes != null && styleAttributes.Count > 0)
            {
                builder = new StringBuilder("style=\"");
                foreach (KeyValuePair<string, object> styleAttribute in styleAttributes)
                {
                    if (styleAttribute.Key == "class")
                    {
                        Class = string.Format("class=\"{0}\"", styleAttribute.Value);
                    }
                    else
                    {
                        builder.Append(styleAttribute.Key + ":" + styleAttribute.Value + ";");                        
                    }
                }
                builder.Append("\"");
            }
            
            return string.Format(ImageTag, Imagelocation, Alt, builder, Class);
        }

        public static string ThumbnailLocation(this HtmlHelper Helper,                                 
                                string Controllername,
                                string Action,
                                int Width,
                                int Height,
                                string File)
        {
            return string.Format("/{0}/{1}/{2}/{3}/{4}", Controllername, Action, Width, Height, File);
            
        }
    }
}

Below is the code for registering the route for image resizing in the global.asax. I decided to make a Service route which then had an action to resize the images setting default image, width and height.

public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
    routes.MapRoute(
        "Service",                                              // Route name
        "Service/{action}/{width}/{height}/{file}",              // URL with parameters
        new { controller = "Service", action = "ResizeImage", width = 500, height = 500, file = "geekhard.jpg" }
    );
    routes.MapRoute(
        "Default",                                              // Route name
        "{controller}/{action}/{id}",                           // URL with parameters
        new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
    );
}

When creating the ServiceController remember to derive from the ImageController to allow access to the Thumbnail result.
 
public class ServiceController : ImageController
{
    public ThumbnailResult ResizeImage(int width, int height, string file)
    {
        return Thumbnail(width, height, file);
    }
}


Below is how to call the html helper from the view. I feel that the html helpers have only 50% of the functionality needed allowing you to set inline styles as well as classes but missing is the ability to set attributes and events. (Everything rendered is wc3 compliant).
<asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">

    
    <%=Html.Thumbnail("Service", "ResizeImage", 300, 399, "geekinside.jpg", "Geek", 
                    new Dictionary<string, object> { 
                    { "border", "solid 1px black" }, 
                    { "margin-top", "0px" }, 
                    { "float", "left" } })%>
                    
    <%=Html.Thumbnail("Service", "ResizeImage", 250, 399, "geekinside.jpg", "Geek", 
                        new Dictionary<string, object> { 
                        { "float", "left" }, 
                        { "margin", "20px" }, 
                        { "margin-top", "0px" }, 
                        { "border", "solid 1px red" } })%>
                        
                        
    <%=Html.Thumbnail("Service", "ResizeImage", 200, 399, "geekinside.jpg", "Geek", 
                        new Dictionary<string, object> { 
                        { "float", "left" }, 
                        { "margin", "20px" }, 
                        { "margin-top", "0px" }, 
                        { "border", "solid 1px red" } })%>
                        
    <%=Html.Thumbnail("Service", "ResizeImage", 150, 399, "geekinside.jpg", "Geek", 
                        new Dictionary<string, object>())%>
                        
    <%=Html.Thumbnail("Service", "ResizeImage", 100, 399, "geekinside.jpg")%>


    <div style="clear:both;  height:50px; padding-top:25px;"><hr /></div>
    
    <%=Html.Thumbnail("Service", "ResizeImage", 350, 111, "geekhard.jpg", "lkzf", 
              new Dictionary<string, object> { 
              { "margin-left", "20px" }, 
              { "border", "solid 1px red" }, 
              { "class", "Left" } })%>

    <img src="<%=Html.ThumbnailLocation("Service", "ResizeImage", 100, 300, "geekhard.jpg")%>" 
    alt="my image" class="Left" />

    <img src="
    <%=Url.Action("ResizeImage", "Service", new {width = 500, height = 100, file="geekhard.jpg" }) %>"
    alt="New" class="Left"  />
    
    <div style="clear:both;"></div>

</asp:Content>

Here is the output! This is just the beginning of my ImageController implementation. I have plans to introduce more ActionResults including:-DropShadowResult, RotateResult and RemoveGammaResult (anyone who has worked with IE will love this one!).

image

Download The Code

Twitter Twitter Twitter Twitter Twitter
November 16, 2009 20:43 by DanWatson