Sunday, April 26, 2009

Dynamically Generated Images with ASP.NET MVC

For a site I’m working on using ASP.NET MVC, I intended to place a Date badge beside the blog and news posts I’m writing. Since in ASP.NET MVC there’s no notion of custom controls (well, at least not like in WebForms) you’ll have to do this manually, but as it turned out it was pretty easy to do.

What I needed to do was to convert a Date instance, e.g. 04.23.2009 to a user friendly calendar icon like this one:

CalendarIcon

I’ve seen blogs and site using different icon for each day of the month or a similar trick to do this, but why not actually render it using Graphics API and a picture?

The first step was to decide what should be returned in you Controller’s action method, as the action result? Since there’s nothing that returns an Image as the result, let’s create one:

public class ImageResult : ActionResult
{
public Image Image
{
get; set;
}

public ImageFormat ImageFormat
{
get; set;
}

private static Dictionary<ImageFormat, string> FormatMap
{
get; set;
}

static ImageResult()
{
CreateContentTypeMap();
}

public override void ExecuteResult(ControllerContext context)
{
if (Image == null) throw new ArgumentNullException("Image");
if (ImageFormat == null) throw new ArgumentNullException("ImageFormat");

context.HttpContext.Response.Clear();
context.HttpContext.Response.ContentType = FormatMap[ImageFormat];

Image.Save(context.HttpContext.Response.OutputStream, ImageFormat);
}

private static void CreateContentTypeMap()
{
FormatMap = new Dictionary<ImageFormat, string>
{
{ ImageFormat.Bmp, "image/bmp" },
{ ImageFormat.Gif, "image/gif" },
{ ImageFormat.Icon, "image/vnd.microsoft.icon" },
{ ImageFormat.Jpeg, "image/Jpeg" },
{ ImageFormat.Png, "image/png" },
{ ImageFormat.Tiff, "image/tiff" },
{ ImageFormat.Wmf, "image/wmf" }
};
}
}
pretty easy, ha? You just need to specify the image and the format and it is rendered to the HttpContext as an image. The controller action to render the text would something like this:
public ActionResult GetCalendarBadge(DateTime displayDate)
{
Graphics graphics = ?;
Bitmap bmp = ?;

//Draw using graphics

//Direct the output to the bitmap

return new ImageResult { Image = bmp, ImageFormat = ImageFormat.Png };
}
and on the view:
<% foreach(var item in this.Model.News) %>
<%= Html.Image<NewsController>(o => o.GetCalendarBadge(item.DisplayDate), 100, 100) %>

<% } %>

CalendarNow, to make things easier, let’s use an existing bitmap as our canvas and just draw the values on it. To do this, add an existing image to your project and load it. The rest is just GDI API that renders the date values according to user’s Cultural setting.


public ActionResult GetCalendarBadge(DateTime displayDate)
{
var bmp = Images.Calendar;
var g = Graphics.FromImage(bmp);

using (var genericFormat = GetStringFormat())
{
var yearRect = new Rectangle(24, 13, 40, 15);
var dayOfMonthRect = new Rectangle(10, 29, 70, 44);
var dayNameRect = new Rectangle(10, 30, 70, 10);
var monthNameRect = new Rectangle(10, 61, 70, 10);

using(var headerFont = new Font("Tahoma", 7.5f, FontStyle.Regular))
using(var footerFont = new Font("Tahoma", 7.5f, FontStyle.Regular))
using (var dayFont = new Font("Tahoma", 14, FontStyle.Bold))
{
var day = CurrentCulture.Calendar.GetDayOfMonth(displayDate).ToString();
var month = CurrentCulture.Calendar.GetMonth(displayDate);
var year = CurrentCulture.Calendar.GetYear(displayDate).ToString();

var weekDay = CurrentCulture.Calendar.GetDayOfWeek(displayDate);
var dayName = CurrentCulture.DateTimeFormat.GetDayName(weekDay);
var monthName = CurrentCulture.DateTimeFormat.GetAbbreviatedMonthName(month);

g.DrawString(year, headerFont, Brushes.White, yearRect, genericFormat);
g.DrawString(day, dayFont, Brushes.Black, dayOfMonthRect, genericFormat);
g.DrawString(dayName, footerFont, Brushes.Black, dayNameRect, genericFormat);
g.DrawString(monthName, footerFont, Brushes.Black, monthNameRect, genericFormat);
}
}

return new ImageResult { Image = bmp, ImageFormat = ImageFormat.Png };
}

CalendarBadge

Note that CurrentCulture property returns the running user’s CultureInfo which will help “translating” the date value correctly for different Cultures. What we achieved is a nice calendar badge with render date values and it is not even constrained to our Canvas size. Hope this helps.
Submit this story to DotNetKicks Shout it

3 comments:

Anonymous said...

I was wondering how I can use your example to load an image from the database.
I have a field that contains the byte arrayof the image so how can I load it into the BitmapData?

Thanks

Leonardo Micheloni said...

Very useful thank you.

l0t3k said...

Good job. Im currently working on a ThumbnailActionResult...