Page 1 of 1

Working with images via HttpHandlers

#1 PsychoCoder  Icon User is offline

  • Google.Sucks.Init(true);
  • member icon

Reputation: 1633
  • View blog
  • Posts: 19,853
  • Joined: 26-July 07

Post icon  Posted 31 May 2008 - 10:46 AM

In a previous tutorial I discussed manipulating images using GDI+, today we're going to look at working with images using a custom HttpHandler that implements the IHttpHandler Interface.

This tutorial is on the advanced side and makes a couple assumptions:So if you are new to the programming/C# world some of the items covered may seem complicated, but I will do my best to provide links to the items we are looking at. This will be a multi-part series on manipulating images with out HttpHandler, in this we will look at creating nice thumbnails of our images with the HTTP request. This may end up being quite long, so setting in for some reading and learning.

First we need is our HttpHandler class. This class will be marked with the abstract modifier so we can inherit from it in our thumbnail class. First, as with any class we build in C#, we need references to the appropriate Namespaces, for this class we need the following Namespaces


using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Net;




When we inherit from a base class (in our case an Interface) you need to include that in your class's signature. You can name your class anything you like, mine just so happens to be named RLMHttpHandler


public abstract class RLMHttpHandler : IHttpHandler
{
     //class body will go here
}




In our base class we have 3 properties, 2 will be overridden in our thumbnail class, the other is read-only and always returns true (as we want this class to be reusable)


#region Properties
/// <summary>
///does handler need authentication
/// </summary>
public abstract bool RequiresAuthentication { get; }

/// <summary>
///get the MIME Type.
/// </summary>
public abstract string ContentMimeType { get; }

/// <summary>
/// is this reusable
/// </summary>
public bool IsReusable
{
    get { return true; }
}
#endregion Properties




Since our class is marked with abstract we are able to have abstract methods in our class. Marking a method with abstract means those methods must be implemented by any class that inherits our handler class. When we implement them in our thumbnail class we will need to mark them with the override modifier so that we can extend the methods. Here are the two abstract methods


/// <summary>
///method for handling the request This is where you put your business logic.
/// </summary>
/// <param name="context">the current HttpContext</param>
protected abstract void HandleRequest(HttpContext context);

/// <summary>
/// validates the parameters.  Inheriting classes must implement this and return 
/// true if the parameters are valid, otherwise false.
/// </summary>
/// <param name="context">the current HttpContext</param>
/// <returns></returns>
public abstract bool ValidateParameters(HttpContext context);




Next we will look at the main (primary) method of our handler class, aptly named ProcessRequest. Here is where we will set the cache policy (meaning to allow or disallow caching of the images being processed), we will make sure we have access to the current resource and make sure all request parameters are valid.


/// <summary>
///processs the incoming HTTP request.
/// </summary>
/// <param name="context">the current HttpContext</param>
public void ProcessRequest(HttpContext context)
{
    //set the cache policy
    this.SetResponseCachePolicy(context.Response.Cache);

    //make sure all parameters were validated successfully,
    //otherwise respond with an error
    if (!(this.ValidateParameters(context)))
    {
        this.RespondInternalError(context);
        return;
    }

    //make sure we have access to the resource
    if (this.RequiresAuthentication && (!(context.User.Identity.IsAuthenticated)))
    {
        //no access so return a forbidden access error
        this.RespondForbidden(context);
        return;
    }

    context.Response.ContentType = this.ContentMimeType;
    this.HandleRequest(context);
}




Now for the four methods that are called upon in our ProcessRequest method. First is the SetResponseCachePolicy method, this is where we tell our handler whether it's safe to cache the thumbnails or not


/// <summary>
/// set the cache policy.  
/// Unless overridden handler will not allow a response to be cached.
/// </summary>
/// <param name="cache">the current HttpContext</param>
public void SetResponseCachePolicy(HttpCachePolicy cache)
{
    cache.SetCacheability(HttpCacheability.NoCache);
    cache.SetNoStore();
    cache.SetExpires(DateTime.MinValue);
}




The next three just work with generating the error message that may (or may not) be needed


/// <summary>
/// method for responding to any error in the current request
/// </summary>
/// <param name="context">the current HttpContext</param>
protected void RespondInternalError(HttpContext context)
{
    context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
    context.Response.End();
}

/// <summary>
/// method to send forbidden response when request is trying
/// to access a forbidden resource
/// </summary>
/// <param name="context">Context.</param>
protected void RespondForbidden(HttpContext context)
{
    context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
    context.Response.End();
}

/// <summary>
/// method to send a file not found response if the
/// image isng found
/// </summary>
/// <param name="context">Context.</param>
protected void RespondFileNotFound(HttpContext context)
{
    context.Response.StatusCode = (int)HttpStatusCode.NotFound;
    context.Response.End();
}




Okay, we now have our HttpHandler completed, it is ready for us to implement in our thumbnail class, so lets create the thumbnail class. First our Namespace references


using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Web;
using System.Web.UI;
using System.Drawing.Imaging;
using System.Reflection;
using System.Drawing;




In out thumbnail class we offer different size thumbnails to be generated, we handle this with a simple enumeration


#region Enumerations
/// <summary>
///enum for handling the size of the thumbnail
/// to be generated by this request
/// </summary>
internal enum Size
{
    Small = 72,
    Medium = 144,
    Large = 288
}
#endregion Enumerations




That takes care of the thumbnail size problem. Next we have a couple variables, marked with the const modifier, telling the class that these values can never change, and a couple class level variables


#region Constants
// Declare and define global constants that come
//from the QueryString of thr current request
private const string IMG_PARAM = "img"; // image parameter
private const string SIZE_PARAM = "sz"; // size parameter

//default thumbnail
private const string DEFAULT_THUMBNAIL = "NotAvailable.gif";
#endregion

#region Globals
/// <summary>
/// MIME text for this request
/// </summary>
private string _mimeText = "image/gif";

/// <summary>
/// ImageFormat for this request
/// </summary>
private ImageFormat _formatType = ImageFormat.Gif;

/// <summary>
/// size of this thumbnail
/// </summary>
private Size _sizeType = Size.Small;
#endregion




Now we get into the heart of our thumbnail class. First we will look at the methods for our thumbnail class, then the override methods from our HttpHandler class. The methods we will be looking at are
  • ValidImage: This method determines if we're dealing with a valid image file. Here, if we have a valid image we will set out MimeText and FormatType global values. If it's not a valid image file we return false to stop the process
  • SetThumbnailSize: This method will set the size of the thumbnail we are creating
  • CreateThumbnail: This method will do the creation of the thumbnail. This method will use math to determine, based on the current size of the image, what the proper dimensions are for an accurate thumbnail image. Unlike the first 2 this method returns an Image type rather than being a void
  • GetDefaultImage: This method will get the default image we specify. This only comes into play if the image we're looking for doesnt exist
So lets look at them one at a time:


ValidImage
/// <summary>
///method to determine if we have a valid image or not
/// </summary>
/// <param name="fileName">File name from the img parameter.</param>
/// <returns>
/// </returns>
private bool ValidImage(string file)
{
    //get the extension of the file name passed
    string extension = Path.GetExtension(file).ToLower();
    //bool to hold whether it's valid or not
    bool isValid = false;

    //now determine the extension
    switch (extension)
    {
        case ".jpg":
        case ".jpeg":
            isValid = true;
            this._mimeText = "image/jpeg";
            this._formatType = ImageFormat.Jpeg;
            break;
        case ".gif":
            isValid = true;
            this._mimeText = "image/gif";
            //this._formatType = ImageFormat.Gif;
            this._formatType = ImageFormat.Jpeg;
            break;
        case ".png":
            isValid = true;
            this._mimeText = "image/png";
            //this._formatType = ImageFormat.Png;
            this._formatType = ImageFormat.Jpeg;
            break;
        default:
            //since it's not an image extension we return false
            isValid = false;
            break;
    }
    return isValid;
}





SetThumbnailSize
/// <summary>
/// Sets the size of the thumbnail base on the size parameter.
/// </summary>
/// <param name="value">The size parameter.</param>
private void SetThumbnailSize(string value)
{
    //int to hold the converted size value
    int size;

    //if we cant parse the value (meaning no valid number provided) we set
    //the thumbnail size to medium
    if (!(Int32.TryParse(value.Trim(), System.Globalization.NumberStyles.Integer, null, out size))) size = (int)Size.Medium;

    //since the value parsed correctly now we set the size
    try
    {
        this._sizeType = (Size)size;
    }
    catch
    {
        this._sizeType = Size.Medium;
    }
}




CreateThumbnail
/// <summary>
/// This method generates the actual thumbnail.
/// </summary>
/// <param name="src"></param>
/// <returns>Thumbnail image</returns>
private Image CreateThumbnail(Image src)
{
    int max = (int)this._sizeType;

    int width = src.Width;
    int height = src.Height;

    if (width > max)
    {
        height = (height * max) / width;
        width = max;
    }

    if (height > max)
    {
        width = (width * max) / height;
        height = max;
    }

    // The third parameter is required and is of type delegate.  Rather then create a method that
    // does nothing, .NET 2.0 allows for anonymous delegate (similar to anonymous functions in other languages).
    return src.GetThumbnailImage(width, height, delegate() { return false; }, IntPtr.Zero);
}




GetDefaultImage
/// <summary>
/// Get default image.
/// </summary>
/// <remarks>
/// This method is only invoked when there is a problem with the parameters.
/// </remarks>
/// <param name="context"></param>
private void GetDefaultImage(HttpContext context)
{
    Assembly assembly = Assembly.GetAssembly(this.GetType());
    Stream imageStream = null;
    Bitmap bitmap = null;
    string file = string.Format("{0}{1}{2}", DEFAULT_THUMBNAIL, (int)this._sizeType, ".gif");

    imageStream = assembly.GetManifestResourceStream(assembly.GetName().Name + file);
    if (imageStream != null)
    {
        bitmap = (Bitmap.FromStream(imageStream) as Bitmap);
        bitmap.Save(context.Response.OutputStream, this._formatType);

        imageStream.Close();
        bitmap.Dispose();
    }
}




Now we have our image manipulation methods accounted for, and by now you're asking (yes I can hear you) about the HttpHandler class we are supposed to be using. Well now we get to that, now we will take a look at our base class methods and getting them to dow hat we want, make thumbnail images. First we need to override the abstract properties from our handler class, the first will return false (requires authentication property), and the MIME text we set in ValidImage


/// <summary>
///tell base class we dont need authentication
/// </summary>
/// <value>
/// </value>
public override bool RequiresAuthentication
{
    get { return false; }
}

/// <summary>
//tell the base class our MIME text
/// </summary>
public override string ContentMimeType
{
    get { return this._mimeText; }
}




Now we have two abstract methods from our base class we need to account for. We're going to go the lazy way for ValidateParameters and just always return true. We do this because if there is a problem we just return a default image in it's place.


/// <summary>
///always return true for this, GetDefaultImage will handle any errors
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public override bool ValidateParameters(HttpContext context)
{
    return true;
}




Last but not least we need to override the abstract HandleRequest method of our base class. Here we will check the size parameter passed, along with the image parameter passed. If image parameter isnt present we simply retrieve the default image, if no size is provided we default to a medium thumbnail.


/// <summary>
///override the HandleRequest from our base class and use it
/// to create our thumbnails
/// </summary>
/// <param name="context"></param>
protected override void HandleRequest(HttpContext context)
{
    //if no size is provided default to small
    if (string.IsNullOrEmpty(context.Request.QueryString[SIZE_PARAM])) this._sizeType = Size.Small;
        //otherwise set out thumbnail size
    else this.SetThumbnailSize(context.Request.QueryString[SIZE_PARAM]);

    //if no image was provided get the default image
    if ((string.IsNullOrEmpty(context.Request.QueryString[IMG_PARAM])) ||   (!(this.ValidImage(context.Request.QueryString[IMG_PARAM])))) this.GetDefaultImage(context);
    else
    {
        string file = context.Request.QueryString[IMG_PARAM].Trim().ToLower().Replace("\\", "/");

        //if our parameter doesnt contain a forward slash we need to add one
        if (file.IndexOf("/") != 0)  file = "/" + file;

        //check to see if the image exist, if not grab the default image
        if (!(File.Exists(context.Server.MapPath("~" + file))))
        {
            this.GetDefaultImage(context);
        }
        else
        {
            //image exists so create our thumbnail and save it to
            //the current HttpContext's output stream
            using (System.Drawing.Image im = System.Drawing.Image.FromFile(context.Server.MapPath("~" + file)))
            using (System.Drawing.Image tn = this.CreateThumbnail(im))
            {
                tn.Save(context.Response.OutputStream, this._formatType);
            }
        }
    }
}




Now our code for creating thumbnails via a Http Request, but unfortunately we're not done there. First, in the web.config file of the web application we're using this in we need to register a new HttpHandler. The syntax for adding a new HttpHandler to your web.config is


<httpHandlers>
	 <add verb="verb list" 
	   path="path/wildcard" 
	   type="type,assemblyname" 
	   validate="true|false"/>
</httpHandler>




The one I added to my web.config look like this


<httpHandlers>
	<add verb="*" path="ImageThumbnail.ashx" type="RLM.ImageHandler.Thumbnail, RLMImageHandler"/>
</httpHandlers>




Where RLM.ImageHandler.Thumbnail is the name of the type Im using and RLMImageHandler is the actual name of the assembly. ImageThumbnail.ashx is where the requests will be sent and processed (though ImageHandler.ashx isnt an actual, physical page in our application).

Now all we need is a page to work with. We will use an Image Server Control for displaying our image. I will show a short example I used when testing this whole process. I simply read all the images from a directory, thehn bind those results to a Repeater Control.

First the code to read the images from a directory


protected void Page_Load(object sender, EventArgs e)
{
    //create a new IList for holding the image names
    //<cref=http://msdn.microsoft.com/en-us/library/5y536ey6.aspx</cref>
    IList<FileInfo> fileList = new List<FileInfo>();
    //create a filter so we only get back image files
    string fileExt = "*.jpg,*.png,*.gif";

    //loop through all the items in our "filter"
    foreach (string ext in fileExt.Split(','))
    {
        //array of file information
        FileInfo[] files = new DirectoryInfo(this.Server.MapPath("~/images/tagz")).GetFiles(ext);

        //loop through each file found
        foreach (FileInfo file in files)
        {
            //add it to our list
            fileList.Add(file);
        }
    }

    //bind our repeater
    this.Repeater1.DataSource = fileList;
    this.Repeater1.DataBind();
}




Then when I set up my Repeater it looks like this


<asp:Repeater ID="Repeater1" runat="server">
    <ItemTemplate>
      <div>
        <asp:Image ID="Image2" runat="server" ImageUrl='<%# Eval("Name", "ImageThumbnail.ashx?img=images/tagz/{0}&sz=77") %>' />
        
     </div>
   </ItemTemplate>
</asp:Repeater>



There you have it! Create image thumbnails on the fly using an HttpHandler. Of course the path to your images and such will have to be modified, but that is how you work with images with http handler and requests. I hope you not only enjoyed reading this, but found it informative and useful.

Happy Coding! :)

This post has been edited by PsychoCoder: 31 May 2008 - 10:46 AM


Is This A Good Question/Topic? 0
  • +

Page 1 of 1