Gravatar 201: Advanced Gravatars in ASP.NET - Jon Galloway

Gravatar 201: Advanced Gravatars in ASP.NET

I know that's a dumb title. I just couldn't help myself. This post wraps up my Gravatar trilogy:

Part 1: Adding Gravatars to your ASP.NET site in a few lines of code - It's easy to add avatars to your site

Part 2: Avatars? Isn't that some kind of D&D comic book stuff? - Avatars can help make your community website feel more like a community and less like a website

Technical recap on setting up Gravatars

Since this is a 201 class, we're going to recap the technical details on integrating with Gravatars - for more info, see the first post. The short version - a Gravatar ID is just an MD5 hash of the user's e-mail address, which can be done in one line of code:

string hash = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(email.Trim(), "MD5");

Then you reference the image using that MD5 as the Gravatar ID:

<img src="http://www.gravatar.com/avatar.php?gravatar_id=63c32b4489d13b17d23fd9db1505bdf9">

And we're done:

 

Here's how that code works out (assuming image with src databound to GetGravatarUrl):

protected string GetGravatarUrl(object dataItem) { string email = (string)DataBinder.Eval(dataItem, "email"); string hash = System.Web.Security.FormsAuthentication.HashPasswordForStoringInConfigFile(email.Trim(), "MD5"); hash = hash.Trim().ToLower(); string gravatarUrl = string.Format( "http://www.gravatar.com/avatar.php?gravatar_id={0}&rating=G&size=60", hash); return gravatarUrl; }

Well, that was easy? Why mess with that?

It is pretty neat, huh? We take an e-mail, and in a few lines of code we've got a avatar images. Well, that might work out well for you, but there's a catch. Since Gravatar is serving the images instead of you, the image load time is out of your hands. Like a lot of good internet services, Gravatar slowed way down as it got more popular. A major update came out earlier this year which greatly improved scalability. For instance, right when you upload an image, it's scaled to all possible sizes - from 1x1 to 80x80 - and mirrored to two Amazon S3 servers. Despite all that, though, it ran a bit slow in some of my tests.

When I noticed that, I checked in with my new favorite helpline - Twitter. Kevin Fricovsky recommended I look at the code for DotNetKicks. (forehead slap - I'm on DotNetKicks daily, but didn't think to look at the code).

Gravatars in open source ASP.NET projects

I'll be referencing Gravatar code in some open source projects - dasBlog (weblog system), SubText (weblog system), and DotNetKicks (community based development news site). These are all BSD licensed projects, so you can use this code in your projects - open or closed source. Links to source code below are to HTML views, so please don't hesitate to click through. It's good to read code!

Identicons

SubText's avatars use a smart mix of Gravatars and Identicons to ensure everyone gets some sort of personality. The Gravatar image URL allows for an optional parameter which specifies a default image if no Gravatar is found for the provided e-mail, and Identicons are images which are based on the user's IP:

identicon-samples

So it all works out pretty well as long as you're recording IP for anonymous users. You want a unique image for each user - if you've got an e-mail and that e-mail loads a Gravatar, it gets shown; otherwise we've at least got an Identicon. So, here's how that looks in Phil's weblog:

CropperCapture[81]

You can check out the code in the SubText solution. Comments.cs displays comments (including avatars) and you can find the Identicon handler on CodePlex. The end result is an image source URL which looks like this (line break added to prevent line wrapping madness):

http://www.gravatar.com/avatar.php?gravatar_id=328bcf45b24e219e3ea9f4c57017b9b1&size=80 &default=http%3a%2f%2fwww.haacked.com%2fimages%2fIdenticonHandler.ashx%3fsize%3d80%26code%3d554922564

CSS Background Image

Okay, I thought of this one on my own, but it's pretty obvious. I set the background image for the image tag via CSS, which causes that image to be loaded immediately, but it's replaced if the source is available. That's an easy way of using a default image that doesn't require waiting for a response from the Gravatar site. Of course, the best would be to also pass that image as the default to Gravatar.

<img style="background-image: url(http://website.com/images/anonymous.gif);" border="0" alt='<%# Eval("UserName") %>' src="<%# GetGravatarUrl(Container.DataItem) %>" />

Using a local Gravatar Cache

DotNetKicks takes Gravatars to a new level. One of the cool features DotNetKicks adds is caching - when Gravatar images are loaded, a local copy is saved to a file cache. The entire DotNetKicks system is a case study in building a high performance ASP.NET site, especially when it comes to caching. Gavin's Reluctant Cache Pattern, for instance, only adds items to cache after requests per minute go over a threshold - that prevents cache thrash when spiders rip through a site. The Gravatar control and handler are a nice example of caching.

First, Gravatar.cs writes out the image tag. Then ViewGravatar.ashx.cs does the heavy lifting, and the code explains itself well enough that I'm just going to quote it here:

using System; using System.Data; using System.Web; using System.Collections; using System.Web.Services; using System.Web.Services.Protocols; using System.IO; using Incremental.Kick.Web.Helpers; namespace Incremental.Kick.Web.UI.Services.Images { public class ViewGravitar : IHttpHandler { private const int GRAVATAR_CACHE_DURATION_IN_MINUTES = 60; public void ProcessRequest(HttpContext context) { context.Response.ContentType = "image/JPEG"; int size = int.Parse(context.Request["size"]); string gravatarID = context.Request["gravatar_id"]; if (size != 16 && size != 50 && size != 80) throw new ArgumentException("The size must be either 16, 50 or 80"); string gravatarFileName = String.Format("{0}_{1}.jpg", gravatarID, size); string cachedGravatarFolderPath = Path.Combine(context.Request.PhysicalApplicationPath, @"Static\Images\Cache\Gravatars\"); string defaultGravatarFolderPath = Path.Combine(context.Request.PhysicalApplicationPath, @"Static\Images\Cache\DefaultGravatars\"); string cachedGravatarFilePath = Path.Combine(cachedGravatarFolderPath, gravatarFileName); string gravatarCopyPath = cachedGravatarFilePath.Replace(".jpg", ".copy.jpg"); string gravatarToReturnPath = cachedGravatarFilePath; if (File.Exists(gravatarToReturnPath) && (File.GetLastWriteTime(gravatarToReturnPath).AddMinutes(GRAVATAR_CACHE_DURATION_IN_MINUTES) < DateTime.Now)) { if(!File.Exists(gravatarCopyPath) && new FileInfo(cachedGravatarFilePath).Length > 0) File.Copy(cachedGravatarFilePath, gravatarCopyPath, true); //create a copy for tempory serving try { File.Delete(cachedGravatarFilePath); } catch (System.IO.IOException) { } gravatarToReturnPath = gravatarCopyPath; } if (File.Exists(gravatarToReturnPath)) { context.Response.Cache.SetExpires(DateTime.Now.AddHours(24)); context.Response.Cache.SetValidUntilExpires(true); context.Response.Cache.SetCacheability(HttpCacheability.Public); } else { if (File.Exists(gravatarCopyPath)) gravatarToReturnPath = gravatarCopyPath; else gravatarToReturnPath = Path.Combine(defaultGravatarFolderPath, String.Format("gravatar_{0}.jpg", size)); //Asynchronously download the gravatars to the cache GravatarHelper.DownloadGravatar_Begin(gravatarID, size, cachedGravatarFilePath); } try { context.Response.WriteFile(gravatarToReturnPath); } catch(System.IO.IOException) { //The file may be locked try { context.Response.WriteFile(gravatarCopyPath); } catch (System.IO.IOException) { context.Response.WriteFile(Path.Combine(defaultGravatarFolderPath, String.Format("gravatar_{0}.jpg", size))); } } } public bool IsReusable { get { return false; } } } }

dasBlog keeps it simple

dasBlog keeps it pretty simple - it's pretty close to the code I showed in my first Gravatar post. Here's the current dasBlog file which handles Gravatars; you can see an older version in the swanky Krugle viewer - the Gravatar handling hasn't changed. This is what Scott Hanselman runs his weblog on, and the Gravatars load pretty well. I think the dasBlog implementation is nice, but it's mostly just wiring up the blog and comment information to the Gravatar URL.

Go copy some code and pop some Gravatars on your site!

Published Tuesday, October 2, 2007 12:18 AM by Jon Galloway

Comments

# re: Gravatar 201: Advanced Gravatars in ASP.NET

Thanks Jon - I'm putting Gravatars on our blog today.

BTW... why no Gravatars in your comments here?

Bill

Tuesday, October 2, 2007 11:50 AM by Bill Richardson

# re: Gravatar 201: Advanced Gravatars in ASP.NET

The MD5 hash needs to be lowered to work.  By default the hash is returned as all Upper Case which doesn't show my image.  After lowering the the Hash it worked.

Thursday, November 6, 2008 1:01 PM by Alexander Higgins

# re: Gravatar 201: Advanced Gravatars in ASP.NET

Great. It was just what I needed. Why not pt it on your site?

Monday, February 28, 2011 8:42 AM by Alen