In this recipe, we will create another custom ActionResult
. This one will also render an image, but in this case we will allow the user to set the size of the image.
Nothing is required to get this recipe running other than writing some quick code. Due to the changes of how this recipe works, we won't actually be building off of the last recipe.
models
folder, we create a new class and name it ImageResizeResult
. Then, we set this new class to inherit from ActionResult
.ImageResizeResult.cs:
public class ImageResizeResult : ActionResult {
private
variables for use in our class. These variables will be populated via the constructor when this ActionResult
is put to work.ImageResizeResult.cs:
private string _path; private int _width; private int _maximumHeight; private bool _noZooming;
ActionResult
, you will need to do it through the constructor. For that reason, we will define the constructor next. In our constructor, we will take in the name of the file that is requested (just the filename), which will allow us to hide the location of our files and (if we need to) move them too. We will also take in the height and width of the image that should be returned. We will also allow the requestor to specify whether or not the image can be zoomed (made bigger than the original). Notice that we are using a new .NET 4 optional parameter!ImageResizeResult.cs:
public ImageResizeResult(string fileName, int width, int maximumHeight, bool noZooming = false) { //put this path reference in a config file somewhere! _path = @"{path to files that can be loaded}" + fileName; _width = width; _maximumHeight = maximumHeight; _noZooming = noZooming; }
ImageFormat
instead of String) of the image that we are playing with, so that it is rendered correctly on the client. This code interrogates the file extension of the file that was requested and uses a simple switch
statement to determine the ImageFormat
.ImageResizeResult.cs:
private ImageFormat GetImageFormatFromFile() { //get extension from path to determine contentType string[] parts = _path.Split('.'), string extension = parts[parts.Length - 1]; ImageFormat imageFormat; switch (extension.ToLower()) { case "jpeg": case "jpg": imageFormat = ImageFormat.Jpeg; break; case "gif": imageFormat = ImageFormat.Gif; break; default: throw new NotImplementedException(extension + "not handled"); } return imageFormat; }
Image.FromFile
helper method in the Image
class.ImageResizeResult.cs:
public Image ResizeImage(string imagePathToResize) { Image fullsizeImage; //check for file if (File.Exists(_path)) { fullsizeImage = Image.FromFile(_path); } else { throw new FileNotFoundException(_path); } //load the image from the file system fullsizeImage = Image.FromFile(_path);
ImageResizeResult.cs:
// hack to prevent the internal thumbnail from being used! fullsizeImage.RotateFlip(RotateFlipType.Rotate180FlipNone); fullsizeImage.RotateFlip(RotateFlipType.Rotate180FlipNone);
ImageResizeResult.cs:
// can we zoom this image? if (_noZooming) { if (fullsizeImage.Width <= _width) { _width = fullsizeImage.Width; } }
width
and zoom
to whatever was entered. Instead, we are going to maintain the aspect ratio of the image that we are working with. This means that if we have a square original image, we won't allow the request to turn it into a rectangle there by distorting the image.ImageResizeResult.cs:
// determine new height int newHeight = fullsizeImage.Height * _width / fullsizeImage.Width; if (newHeight > _maximumHeight) { // Resize with height instead _width = fullsizeImage.Width * _maximumHeight / fullsizeImage.Height; newHeight = _maximumHeight; }
Image
by using another helper method—GetThumbnailImage
. Then we will dispose of the instance of the original image. And finally, we will return the resized image.ImageResizeResult.cs:
Image newImage = fullsizeImage.GetThumbnailImage(_width, newHeight, null, IntPtr.Zero); //dispose of the in memory original fullsizeImage.Dispose(); return newImage; }
ExecuteResult
method, which is our hook into the MVC framework. In this method, we will perform all the orchestration that allows us to set the type of image we are returning in the response stream, resize the image, and then write the image down to the client (this part is very much like the last recipe). Pay close attention to where we dispose of the image that we created!ImageResizeResult.cs:
public override void ExecuteResult(ControllerContext context) { byte[] buffer = new byte[4096]; HttpResponseBase response = context.HttpContext.Response; //no context? stop processing if (context == null) throw new ArgumentNullException("context"); //set files content type response.ContentType = "image/" + GetImageFormatFromFile().ToString(); //get the resized image Image resizedImage = ResizeImage(_path); MemoryStream ms = new MemoryStream(); resizedImage.Save(ms, GetImageFormatFromFile()); MemoryStream imageStream = new MemoryStream(ms.ToArray()); ImageResult sizeImageResult sizespecifyingwhile (true) { int read = imageStream.Read(buffer, 0, buffer.Length); if (read == 0) break; response.OutputStream.Write(buffer, 0, read); } response.End(); ms.Dispose(); imageStream.Dispose(); }
ActionResult
should be ready for use. For that reason, open up the HomeController and add a new ActionResult
named GetImage
. This ActionResult
will return our new ImageResizeResult
. In this action, we will allow the requester to pass in the requested image name, the width and height, and whether or not they want to allow zooming. However, we are going to use the new optional parameter functionality for the zooming option and give it a default value of false
.HomeController.cs:
public ImageResizeResult GetImage(string image, int width, int height, bool noZooming = false) { return new ImageResizeResult(image, width, height, noZooming); }
url
parameters, and you should get an image that you can resize till your heart is content! Index.aspx
view. The first one will show that we can take an image, which is normally 200x200, and blow it up to 250x250. The next example will show that we don't allow distortion of the image by trying to set the image's width to 75 and the height to 200. And the last example will show that we can control distortion in another way by not allowing zooming.Index.aspx:
<p> <img src="/home/GetImage?image=me.jpg&width=250&height=250" /> <img src="/home/GetImage?image=me.jpg&width=75&height=200" /> <img src= "/home/GetImage?image=me.jpg&width=1000&height=1000&noZooming=true" /> </p>
This example really isn't all that different from our last recipe, in that we created a class that implemented ActionResult
. We did add a few additional parameters to allow the users to have more control over the process. And then we added some image resize code.
The nice thing about this is that you have a generic image-resizing handler. Also, if all of your images go through either this or the previous recipe, you have a path that you can add to your image processing fairly easily. Something you might want to add down the road is that when an image is requested for the first time in a given size, you might save it to the file system. Then, when it is requested the next time, you might serve that file from the file system rather than attempting a resize every time. Or you might toss the resized file into a cache of some form and load it straight out of memory the next time. The key here is that you have control over how your images are served.