RESTian ratings images

2007-11-12 @ 17:49#

i needed to implement a feature that displayed a set of 'stars' representing the computed customer rating of an image. the database is returning the rating value and i have a fixed set of images (0-5 stars, incremented by .5). all i needed was a clean way to get the proper image from the server.

i implemented a simple .ashx file for ASP.NET that checked for query params and returned the proper image. i return 4XX when needed and even 5XX in the case of really bad stuff. otherwise, i return a clean, cache-able image/png content type every time.

below is the entire code for the handler:

<%@ WebHandler Language="C#" Class="RatingImage" %>

using System;
using System.Web;
using System.IO;

// returns an image that corresponds to the rating passed in
// <img src="ratingimage.ashx?rating=4.5" />
public class RatingImage : IHttpHandler
{
    const double expires = 60 * 60 * 24 * 30;   // 30 days
    const string cache_control_fmt = "public,max-age={0}";
    const string expires_fmt = "{0:ddd dd MMM yyyy HH:mm:ss} GMT";
    const string default_location = "/images/ratings-stars";
    const string url_fmt = "/content/{0}/{1}";
    Utility util = new Utility();

    public void ProcessRequest(HttpContext context)
    {
        try
        {
            context.Response.ContentType = "image/png";
            context.Response.StatusCode = 200;
            context.Response.TransmitFile(string.Format(url_fmt, GetLocation(context),GetFile(context)));

            if (expires != 0)
            {
                context.Response.AddHeader("Cache-Control", string.Format(cache_control_fmt, expires));
                context.Response.AddHeader("Expires", string.Format(expires_fmt, System.DateTime.UtcNow.AddSeconds(expires)));
            }
        }
        catch (HttpException hex)
        {
            context.Response.ContentType = "text/plain";
            context.Response.StatusCode = hex.GetHttpCode();
            context.Response.StatusDescription = hex.Message;
            context.Response.Write(hex.Message);
        }
        catch (Exception ex)
        {
            context.Response.ContentType = "text/plain";
            context.Response.StatusCode = 500;
            context.Response.StatusDescription = ex.Message;
            context.Response.Write(ex.Message);
        }
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }

    string GetFile(HttpContext context)
    {
        double x = Convert.ToDouble(GetRating(context));
        string rtn = string.Empty;

        if (x < .3)
            return "stars00.gif";
        if (x < .7)
            return "stars05.gif";
        if (x < 1.3)
            return "stars10.gif";
        if (x < 1.7)
            return "stars15.gif";
        if (x < 2.3)
            return "stars20.gif";
        if (x < 2.7)
            return "stars25.gif";
        if (x < 3.3)
            return "stars30.gif";
        if (x < 3.7)
            return "stars35.gif";
        if (x < 4.3)
            return "stars40.gif";
        if (x < 4.7)
            return "stars45.gif";

        return "stars50.gif";
    }

    string GetRating(HttpContext context)
    {
        if (context.Request.QueryString["rating"] != null)
            return context.Request.QueryString["rating"];
        else
            throw new HttpException(400, "Missing Rating");
    }

    string GetLocation(HttpContext context)
    {
        if (context.Request.QueryString["location"] != null)
            return context.Request.QueryString["location"];
        else
            return default_location;
    }
}

code