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!).
Download The Code