Published: 28 Sep 2011
By: Justin Schwartzenberger
Download Sample Code

Gain more control over images within an ASP.NET MVC 3 project by creating a controller to handle delivering image file data to the response stream.

Contents [hide]

Introduction

Working with images in a web application can turn from a simple task to a complexity in need of some serious attention as soon as traffic starts to grow or image assets become too vast to reasonably maintain multiple versions of each (large, medium, thumbnail). A general reference to an image in a HTML img tag does not provide a way to control caching or additional headers like the ETag to help site speed performance, nor does it provide a true way to handle resizing (the use of the width and height attributes on the img tag to resize an image is not viable as the full size image is still delivered to the user agent). In addition, it is tightly coupled to the physical location of the image files in the file system as it is typically referencing a directory structure type source URL.

There are several scenarios in which having more control over image file delivery can be advantageous. By writing some code to return image data from a MVC 3 controller action it is possible to inject a layer of control between the HTTP request and the resulting response for an image. From there the sky's the limit. Image content can easily be resized on the fly. Images can be combined to handle scenarios like watermarking. Physical storage locations of the images can be changed without having to update all img tags in the UI layer. It is even possible to block requests for images outside of a site's domain, thus providing a way to stop "image hijacking" by other sites.

I will walk through creating logic within an ASP.NET MVC 3 application to handle serving up images from specified URL routes. The code will support access to images in their default state, but with control over caching and ETags. From there the code will be extended to support image resizing as well as the application of a watermark prior to image data delivery.

Custom ActionResult and Extension Methods

The return of the default image data will be handled by a custom ActionResult that will inherit from the MVC FilePathResult class and override the WriteFile method to inject some additional response header logic for controlling the cache settings. This cache settings logic is going to be reused by another custom ActionResult later in the article when I cover adding support for image resizing and watermarking. As a result, I want to encapsulate that code into a single method call. I can make use of an extension method to do this. The cache policy for the HTTP response can be set via a property named Cache off of an instance of HttpResponseBase, which happens to be the method parameter of the FilePathResult.WriteFile method. My extension method will be built to work off of an instance of the HttpResponseBase class.

Listing 1: HttpResponseExtensionMethod

The SetDefaultImageHeaders extension method configures the cache headers to ensure that the response has some optimization for the user agent. This method can be enhanced or tweaked down the road to handle more functionality at the header level.

Since the constructor of the FilePathResult class (the class my custom action result will inherit from) requires the HTTP header content-type value I am going to need a way to pass in the type of image. An example of the string would be "image/png". I can do this by getting the file extension from the image file name requested. However, there is a catch. For jpeg files the string needs to be "image/jpeg" but most jpeg image files tend to have the jpg extension. I can write an extension method to extract the file extension and at the same time convert the return value to "jpeg" if the extension value is "jpg".

Listing 2: FilesystemExtensionMethods.cs

With these extension methods written I can move on to creating a custom class named ImageFileResult with a constructor that takes in the file name and calls the constructor for the FilePathResult class, making use of the FileExtensionForContentType method to inject the contentType value. The logic in the WriteFile method consists of a call to the new extension method and then a call to the base method.

Listing 3: ImageFileResult.cs

Basic Controller Action

I will create a single controller named ImagesController that will handle all of the image functionality. The first action method that I will need to add is one to deliver a requested image file. This method will take in an image file name, validate the file exists, and return an instance of the ImageFileResult class.

Listing 4: ImagesController.cs

The private method getFullFilePath handles building the full path to the requested file in the file system by using the Server.MapPath method to resolve the location of the /Content/Images directory in the application.

Note

This method is making the assumption that all requested images are going to be stored in that location. Supporting a request of images in different directory structures would simply require a change to the Render action method signature and some routing configurations (beyond the scope of this article).

The imageFileNotAvailable method handles checking that the file exists in the file system. This method can also be used to handle a request validation to ensure that only requests from the current application domain are allowed by checking the Request.ServerVariables["HTTP_REFERER"] with a regular expression to verify that it contains the domain name of the site. The instantiate404ErrorResult method creates a standard not found result message that can be reused by each action method that will handle image delivery.

To use the images controller I want the UI layer to be able to reference image files within an img tag as follows:

To support this structure I need to register a new route in the Globals.asax.cs file:

From here I can start putting image files in the /Content/Images directory and display them in my views with the example img tag above. Each of the images will have corresponding cache headers and ETags to make the user agents happy and provide a performance boost.

Adding Resize Functionality

Rather than rolling my own image resize logic I will use an open-source library named ImageResizer (http://imageresizing.net/) that is available through the NuGet Visual Studio add-on via the main NuGet feed. It can be installed with the Manage NuGet Packages dialog window by doing a search for "imageresizer" or with the Package Manager Console using the following command:

Installing the package will add a reference to the ImageResizer.dll in the Visual Studio project and set its "Copy Local" property to true to ensure that it is deployed to the bin directory when the project is built. The library has a managed API for manipulating images that I will use to handle the image resizing. No other configuration is needed to use the library from within my controller actions.

Since the resized image data will be handled in memory and not written to the file system I will need a new custom action result for working with a byte array. The MVC framework has a class named FileContentResult for delivering byte[] data to the response stream. This class contains the same WriteFile method signature as the FilePathResult class. I can create a new class named DynamicImageResult that inherits from the FileContentResult class, and then override the WriteFile method in the same way as I did in the ImageFileResult to add the header logic.

Listing 5: DynamicImageResult.cs

The constructor has a similar signature to the ImageFileResult class, but also includes the byte[] variable. The call to the base constructor passes the byte[] data through and handles formatting the contentType string off of the file name passed in. The logic in the WriteFile method is the same as the ImageFileResult.WriteFile method to ensure that the custom header settings are applied.

The ImageResizer library has a method for resizing an image and returning a Bitmap object. Since the DynamicImageResult works with a byte[] object I need a method to convert the Bitmap. I can add a new method in the FilesystemExtensionMethods class named ToByteArray that extends off of a Bitmap object and does the conversion. The update to the class:

Back in the ImagesController class I add a method named RenderWithResize to support the dynamic image resizing. This method takes in a width, height and the file name. It does the same full file path and image available check logic as the Render method, then handles the image resize.

The instantiateResizeSettings method takes in a width and height and returns an ImageResizer.ResizeSettings object that is part of the ImageResizer library. The settings are configured with a query string type structure.

The settings "maxwidth" and "maxheight" are used to simulate a containing box. The resized image will not be larger than that size wide or tall. The "quality" is used on jpeg compression and 90 is a sweet spot for good quality and a bit of compression.

The call to ImageBuilder.Current.Build takes in the full file path to the original image and the ResizeSettings object and returns a Bitmap of the image resized. With the image resize complete I can return an instance of the new DynamicImageResult class, passing in the file name and the resized image as a byte[] object (converted using the ToByteArray extension method written earlier).

A new route needs to be added in the Global.asax.cs file (after the "RenderImage" route) to get to the RenderWithResize action method:

Now I can have my UI layer make calls to images and pass in the desired dimensions to get a resized image back:

Adding Watermark Functionality

To watermark logic will consist of resizing the requested image and then applying a watermark image to it before delivering the image data back to the response stream. I will use a new controller action method named RenderWithResizeAndWatermark that will take in the same parameters as the RenderWithResize method (width, height and file name).

The full path to the watermark file is created and the addWatermark method is used to draw the watermark image on top of the resized image. The return value is set to the resizedImage variable.

The watermarkLocation parameter is used to specify the x/y coordinates where the watermark image should be positioned at on the resized image. The code opens the watermark image into an Image object, does a check on the width and height to see if it is larger than the resized image, and resizes the watermark if needed. It then draws the watermark image on top of the resized image starting at the Point location and returns the finished product as a Bitmap object.

There is definitely room for improvement on the watermark positioning and resize logic to better handle cases where the location may result in the watermark being clipped or the ability to keep the watermark at a smaller ratio than the resized image. However, this bit of code illustrates how to get started dynamically combining image data.

The last thing to do is to add the route to support the watermark action method. This route needs to be added after the "RenderImage" route but before the "RenderImageWithResize" route. The final version of the RegisterRoutes method in the Global.asax.cs file:

Now the UI can reference resized images with watermarks like so:

Summary

Just a little bit of heavy lifting and I now have more control over my image content within my ASP.NET MVC 3 web application. I was able to remove the file path dependency from my UI, add some caching headers to make user agents happy, do some on the fly resizing and even apply a watermark. All of this can be accomplished at the application level with no need for any special IIS modules, handlers or other server side stuff.

<<  Previous Article Continue reading and see our next or previous articles Next Article >>

About Justin Schwartzenberger

I have been entrenched in web application development for quite a while and have traversed the syntactic jungles of PHP, classic ASP, Visual Basic, VB.NET, and ASP.NET Web Forms. However, I have found a guilty pleasure in ASP.NET MVC since its beta launch and have since refactored my web stack focus...

This author has published 3 articles on DotNetSlackers. View other articles or the complete profile here.

Other articles in this category


Code First Approach using Entity Framework 4.1, Inversion of Control, Unity Framework, Repository and Unit of Work Patterns, and MVC3 Razor View
A detailed introduction about the code first approach using Entity Framework 4.1, Inversion of Contr...
jQuery Mobile ListView
In this article, we're going to look at what JQuery Mobile uses to represent lists, and how capable ...
Exception Handling and .Net (A practical approach)
Error Handling has always been crucial for an application in a number of ways. It may affect the exe...
JQuery Mobile Widgets Overview
An overview of widgets in jQuery Mobile.
Book Review: SignalR: Real-time Application Development
A book review of SignalR by Simone.

You might also be interested in the following related blog posts


ASP.NET MVC2 Preview 2: Areas and Routes read more
MvcContrib working on Portable Areas read more
MvcContrib version control has moved to GitHub read more
The ASP.NET MVC ActionController The controllerless action, or actionless controller read more
Going Controller-less in MVC: The Way Fowler Meant It To Be read more
ASP.NET MVC 2.0 and VS 2010 plan now public read more
Testability and TDD are not reasons to use the ASP.NET MVC Framework read more
Testability and TDD are not reasons to use ASP.NET MVC read more
You should NOT use ASP.NET MVC if. . . read more
Is ASP.NET MVC a half-baked solution? read more
Top
 
 
 

Discussion


Subject Author Date
placeholder File Exists? Greg Lemons 9/30/2011 11:20 PM
RE: File Exists? Justin Schwartzenberger 10/3/2011 3:27 PM
placeholder reading image from cdn and resizing. Sagar Singh 3/9/2012 2:53 PM
reading image from cdn and resizing. Sagar Singh 3/9/2012 8:38 PM
placeholder How to reference this project. Alex Aranovsky 8/31/2012 12:10 PM

Please login to rate or to leave a comment.