October 2007 - Posts - Jon Galloway

October 2007 - Posts

Silverlight doesn't require any JavaScript

Summary

It's easier to understand Silverlight when you understand that, at its heart, it's a simple browser plug-in. JavaScript is extremely useful for browser detection, and it's the only way to interact with Silverlight 1.0, but JavaScript not at all required to display a Silverlight control with XAML content.

Note: I'm not saying that you shouldn't use Silverlight.js and CreateSilverlight.js to instantiate a Silverlight object - you most definitely should. Rather, for the purpose of understanding what Silverlight is and how it works, I'm illustrating that you can create and use a Silverlight control without any JavaScript at all.

JavaScript - it's so hot these days...

When you start looking at Silverlight code, the first thing you'll probably notice is the JavaScript and JavaScript includes. There's a good article on MSDN which describes Silverlight.js and CreateSilverlight.js; here's my over-simplification:

  • Silverlight.js has one job: safely creates Silverlight plugins when told. It exposes a methods - Silverlight.createObject() 1 - which handles the creation of a Silverlight plugin. It works cross-browser, detects if the version of Silverlight that you're requiring is installed, and can show an install prompt if the browser is supported, but Silverlight isn't installed. Silverlight.js doesn't do anything by itself; someone needs to call createObject(). And that's where CreateSilverlight.js comes into play.
  • CreateSilverlight.js instantiates your control. It sets some properties and calls the createObject() method in Silverlight.js. That's it. If you're using a tool or a designer, this file will be created for you. Otherwise, you can cook it up yourself - it's a single function which calls Silverlight.createObject().2

It's a good system, but it's important to understand that...

You don't have to use a line of JavaScript to display Silverlight content in a page.

If you have Silverlight installed an you're running either IE7 or Firefox 2, save the following text to a file with an .htm extension and open it  in either browser.

<html> <head> </head> <body> <script type="text/xaml" id="xamlContent"> <?xml version="1.0"?> <Canvas xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Rectangle Height="200" Width="200" Stroke="Black" Fill="Wheat" StrokeThickness="5" RadiusX="10.0" RadiusY="10.0"/> <TextBlock Canvas.Top="100" Canvas.Left="10"> No Javascript, wheeeee!!! </TextBlock> </Canvas> </script> <div id="controlHost"> <object id="silverlightControl" type="application/x-silverlight" height="400" width="400"> <param name="Source" value="#xamlContent" /> </object> </div> </body> </html>

You didn't do it, did you? Why not? I made it so incredibly easy! Fine, here's what it looks like in IE7:

Silverlight with inline XAML

So, at its heart, Silverlight is a browser plug-in which renders XAML, and that doesn't require JavaScript.

Aside: don't get too excited about that inline XAML just yet...

There are all kinds of interesting possibilities in using inline XAML, but unfortunately there's a bit of a showstopper3. Firefox has an open bug - Bug 356095 (NPAPI GetProperty grabs garbage depending on <!DOCTYPE> statement in HTML) which prevents it from using inline XAML if a DOCTYPE is defined. And if you don't have a DOCTYPE defined, your page gets rendered in "quirks mode", which basically means the HTML renderer drops back to IE4 / Netscape 4 rendering.

So, what's with the Silverlight JavaScript, then?

It's good stuff. It's doing important work - it's a safe, consistent, cross browser approach to loading a plugin - something I've advocated before with FlashObject for Flash. We've talked about why you don't technically need JavaScript to embed Silverlight controls in your page, but practically dictates that you should use it. I recommend this article for further information on  using CreateSilverlight.js and Silverlight.js, mostly because it has this awesome diagram:

1 There's another method, createObjectEx(), which does exactly the same thing as createObject() does, it just allows you to pass in your parameters in JSON syntax. Here's the entire listing for CreateObjectEx() as shipped in the standard Silverlight.js file:

Silverlight.createObjectEx=function(b) { var a=b,c=Silverlight.createObject(a.source,a.parentElement,a.id,a.properties,a.events,a.initParams,a.context); if(a.parentElement==null)return c };

If you're using the hosted version of Silverlight.js for Silverlight Streaming, createObjectEx() adds in support for the streaming:/ pseudo-protocol. That's fancy-talk for saying it can translate URL's from streaming:/16445/TicTacToe syntax to a real URL on the Silverlight Live Streaming Service content distribution network.

2 Example:

function createSilverlight() { Silverlight.createObject( "TicTacToe.xaml", // Source property value. parentElement, // DOM reference to hosting DIV tag. "SilverlightElement", // Unique plug-in ID value. { // Plug-in properties. width:'1024', // Width of rectangular region of plug-in in pixels. height:'800', // Height of rectangular region of plug-in in pixels. inplaceInstallPrompt:false, // Determines whether to display in-place install prompt if invalid version is detected. background:'white', // Background color of plug-in. isWindowless:'false', // Determines whether to display plug-in in windowless mode. framerate:'24', // MaxFrameRate property value. version:'1.0' // Silverlight version. }, { onError:null, // OnError property value -- event-handler function name. onLoad:null // OnLoad property value -- event-handler function name. }, null, // initParams -- user-settable string for information passing. null); // Context value -- passed to Silverlight.js onLoad event handlers. }

3 Thanks, Will, for the heads up on this one.

Speaking at the Silicon Valley Code Camp on 10/27

I'm presenting two sessions at the Silicon Valley Code Camp on 10/27. So, drop everything, sign up for my sessions, and get to Silicon Valley this weekend!

But first, some quick Q & A time!!!

Question: Why did you wait until Thursday night to blog about talks you're giving on Saturday?
Answer: There was a gigantic wildfire within ten miles of my house all week, and I wasn't totally sure the wife would let me go. Fires are now pretty much under control.

Question: Will your talks be awesome?
Answer: Yes. So awesome you won't believe it.

Publishing a Silverlight 1.1 game with Silverlight Streaming

3:45 PM Saturday | Room 4204

We'll go soup to nuts with a simple Silverlight 1.1 game hosted on Silverlight Streaming, Microsoft's free content distribution network for Silverlight applications. We'll look at the code for a Silverlight maze game, then walk through the steps to publish the application at Silverlight Streaming.

Using SubSonic to built ASP.NET applications that are good, fast, and cheap

5:15 PM Saturday | Room 4204

SubSonic is an ASP.NET framework which helps you to write good web applications quickly. It generates an Active Record based data access layer, then adds controls and utilities that make it easy to build your site the right way. I've contributed to the SubSonic project and have used it in two applications for major clients.
We'll also take a look at an exciting feature which is nearly complete - database migrations.
http://www.subsonicproject.com

Creating Zip archives in .NET (without an external library like SharpZipLib)

Overview

SharpZipLib provides best free .NET compression library, but what if you can't use it due to the GPL license? I'll look at a few options, ending with my favorite - System.IO.Packaging.

SharpZipLib is good, but there's that GPL thing

SharpZipLib includes good support for zip. I've written about it a few times, and I think it's great. Unfortunately, it's under a wacky "GPL but pretty much LGPL" license - it's GPL, but includes a clause that exempts you from the "viral" effects of the GPL:

Linking this library statically or dynamically with other modules is making a combined work based on this library. Thus, the terms and conditions of the GNU General Public License cover the whole combination. As a special exception, the copyright holders of this library give you permission to link this library with independent modules to produce an executable, regardless of the license terms of these independent modules, and to copy and distribute the resulting executable under terms of your choice, provided that you also meet, for each linked independent module, the terms and conditions of the license of that module.

Bottom line In plain English this means you can use this library in commercial closed-source applications.

I'm pretty sure that the reason for this odd "sort-of-GPL" license is because some of the SharpZipLib is based on some GPL's Java code. However, most companies have policies which forbid or greatly restrict their use of GPL code, and for very good reason: GPL has been set up as an alternative to traditional commercial software licensing, and while it's possible to use GPL code in commercial software, it's something that requires legal department involvement. So, my bottom line is that I can't use your code due to your license.

.NET Zip Library

UPDATE: DotNetZip has been released on CodePlex, and the one issue I ran into has been fixed. I'd recommend giving this a try instead of System.IO.Packaging (as I'd originally recommended), because it's a lot easier to use.

The Zip format allows for several different compression methods, but the most common is Deflate. System.IO.Compression includes a DeflateStream class. You'd think that System.IO would include Zip, but... no. The problem is that, while System.IO.DeflateStream can write to a stream, it doesn't write the file headers required for Zip handlers to read them.

Microsoft Interop blog posted a .NET Zip Library which adds the correct headers to the output of a System.IO.Compression DeflateStream.

ZipFile zip= new ZipFile("MyNewZip.zip");
zip.AddDirectory(
"My Pictures", true); // AddDirectory recurses subdirectories
zip.Save();

Note: DotNetZip has been released to CodePlex, and the issue I reported has been fixed. 

This works, but with some caveats. First of all, adding files causes an identical structure to be created in the zip. For instance, if I use the following:

zip.AddFile("C:\My Documents\Sample\File.txt");

The resulting Zip will contain File.txt, but it will be within the \My Documents\Sample\ hierarchy. There's no way to control the structure of the zip file when you add individual files, unless you want to modify the zip library (which is under MsPL license). That proved to be a big problem in my case, because the zip structure I'm creating is pretty rigid. So, if you're just zipping an entire folder full of files, this library may work for you, but if you need more control you may need to modify the library. I'm guessing if this were published on CodePlex it would have been fixed a while ago.

Another larger problem to keep in mind is that stream based compression is much less efficient than file based compression. File compression can optimize the compression used based on the content of all included files; stream based compression compresses data as it comes in, so it can't take advantage of data it hasn't seen yet.

The J# Zip Library

J# has included zip since day one, to keep compatible with the Java libraries. So, if you're willing to bundle the appropriate Java library (specifically, vjslib.dll), you can use the zip classes in java.util.zip. It works, but it seems like a really goofy hack to distribute a 3.6 MB DLL just to support zip.

System.IO.Packaging includes Zip support

In .NET 3.0, you can use the the System.IO.Packaging  ZipPackage class in WindowsBase.DLL. It's just 1.1 MB, and it just seems to fit a lot better than importing Java libraries. It's not very straightforward, but it does work. The "not straightforward" part comes from the fact that this isn't a generic Zip implementation, it's a packaging library for formats like XPS that happen to use Zip.

First, you'll need to find WindowsBase.dll so you can add a reference to it. If it's not on your .NET references, you'll probably find it in C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\WindowsBase.dll.

It's not as simple as it should be, but it does work. Here's a sample that creates a Zip archive and adds two files:

 

using System;
using System.IO;
using System.IO.Packaging;

namespace ZipSample
{
class Program
{
static void Main(string[] args)
{
AddFileToZip(
"Output.zip", @"C:\Windows\Notepad.exe");
AddFileToZip(
"Output.zip", @"C:\Windows\System32\Calc.exe");
}

private const long BUFFER_SIZE = 4096;

private static void AddFileToZip(string zipFilename, string fileToAdd)
{
using (Package zip = System.IO.Packaging.Package.Open(zipFilename, FileMode.OpenOrCreate))
{
string destFilename = ".\\" + Path.GetFileName(fileToAdd);
Uri uri
= PackUriHelper.CreatePartUri(new Uri(destFilename, UriKind.Relative));
if (zip.PartExists(uri))
{
zip.DeletePart(uri);
}
PackagePart part
= zip.CreatePart(uri, "",CompressionOption.Normal);
using (FileStream fileStream = new FileStream(fileToAdd, FileMode.Open, FileAccess.Read))
{
using (Stream dest = part.GetStream())
{
CopyStream(fileStream, dest);
}
}
}
}

private static void CopyStream(System.IO.FileStream inputStream, System.IO.Stream outputStream)
{
long bufferSize = inputStream.Length < BUFFER_SIZE ? inputStream.Length : BUFFER_SIZE;
byte[] buffer = new byte[bufferSize];
int bytesRead = 0;
long bytesWritten = 0;
while ((bytesRead = inputStream.Read(buffer, 0, buffer.Length)) != 0)
{
outputStream.Write(buffer,
0, bytesRead);
bytesWritten
+= bufferSize;
}
}
}
}

 

Zip

One weird side-effect of using the ZipPackage to create Zips is that Packages contain a content type manifest named "[Content_Types].xml". If you create a ZipPackage, it will automatically include "[Content_Types].xml"., and if you try to read from a ZIP file which doesn't contain a file called "[Content_Types].xml" in the root, it will fail.

You'll notice that the compression in my test is not that great. In fact, pretty bad - Notepad.exe got bigger. Binary files don't compress nearly as well as text-based files - for example, I tested on a 55KB file and it compressed to less than 1KB - but the compression in this library doesn't appear to be fully implemented yet. For example, the CompressionOption enum includes CompressionOption.Maximum, but that setting is ignored. Normal is the best you'll get right now.

Another possible reason for low compression ratios in this sample is that I'm adding files separately rather than adding several files at a time. As I mentioned earlier, Zip compression works better when it has access to the entire file or group of files when creating the archive.

You can use the packaging library for your own file format. For example, here's an example that stores object state using XmlWriters to write to a Zip stream.

But where's System.IO.Zip?

That's a good question. All the Zip handling in System.IO.Packaging is in an internal class MS.Internal.IO.Zip. It would have been a lot more useful to implement a public System.IO.Zip which was used by System.IO.Packaging so that we could directly create and access Zip files without pretending we were creating XPS packages with manifests and Uri's.

I'm an ASP.NET MVP

I'm very happy to announce I was selected as an ASP.NET MVP. Most software developers I've told about this, after congratulating me, confess that they don't really know what an MVP is. I wasn't 100% sure myself. Here's what the MVP site says:

Thank you for the significant contributions that you make to technical communities by sharing your high quality, real world expertise.... Your participation in these communities promotes the free and objective exchange of knowledge. You are recognized in technical communities as a reliable, independent expert, benefiting all who participate in communities. Your expert advice and influence in technical communities help users adopt new technology and help Microsoft understand and meet our customers' needs.

I really like helping people, so it's great hear that I've been helpful.

Here's another, more selfish reason that I love sharing information about software development: There's just so much unrealized potential in the huge network of computers that surround us, doing the equivalent of blinking "12:00 AM" like so many unprogrammed VCR's. I love to write code, to be part of the effort to realize that incredible potential, but the amount of code I'll write in my career is limited. By sharing information and helping others get things done, I feel like I'm cheating the clock a bit.

Posted by Jon Galloway | 3 comment(s)
Filed under: ,

Our ASP.NET book is out. Read some chapters for free!

If you don't know, now you know

I'm told that there are still a few people who don't read CodingHorror.com and Haacked.com. These people call themselves the Amish.

Well, there may be a few more folks out there with live internet connections who for some reason or another missed Phil's and Jeff's announcements that the book we co-authored with Scott Allen and Wyatt Barnett has been unleashed on an unsuspecting world. So, to these people, I say:

Our book, The ASP.NET 2.0 Anthology, is out. I have felt the heft of all 596 pages, and have vanity shopped the Borders bookstore to see the book on the shelf (they actually had two copies, w00t!).

ASP.NET 2.0 Anthology

ASP.NET 2.0? I wanted black-belt ASP.NET 3.5 stuff!

Well, yeah, that's the title. That's what the code samples are in. But you know, there are parts of it that could just easily fit into an ASP.NET 1.1 or 1.0 Anthology. Feel better?

Oh, you're thinking about the impending release of ASP.NET 3.5? Hmm... Well, as I said, many of the solutions we present would work with little modification in ASP.NET 1.1, and will work in ASP.NET 3.5 for the same reason: this book's not one of those "here are the two-hundred-and-thirty-five different properties of the GridView control" kinds of books. This books is about our favorite solutions to the problems you'll face in your day to day work, struggling to build great ASP.NET applications quickly.

There are a few .NET 3.5 things which you won't find in this book - notably Linq and the ListView control. And, no, there's no Silverlight in here. So if you're looking for a rundown on using ListView ItemTemplates, this isn't the book for you. That also goes if you're looking for an overview of setting up an ASP.NET 1.0 DropDownList control. While we do have a few "overview" chapters - declarative databinding, membership, and advanced GridView tricks - this book isn't really driven by the controls in the latest framework release.

Oh, another thing this books not about - black-belt hacks for the sake of feeling like a black-belt coder. We've got some slick code in the book, but one thing we've all realized is that the best solution is often the simplest solution that works. This isn't about black-belt code, it's about using black-belt knowledge of ASP.NET to avoid writing unnecessary code. So while there are plenty of code samples I'm proud of, the parts of this book I'm happiest with are the concepts.

So, what is the book about, then?

Well, we talk about some of the more timeless concerns of building good ASP.NET web applications. Examples:

  • There's a chapter on search engine optimization for ASP.NET, focused on the good kind of SEO - making sure that you're making it easy for search engines to find your content
  • Scott Allen wrote an excellent chapter on JavaScript and AJAX. Of course, it covers ASP.NET AJAX and the AJAX Control Toolkit, but he also talks about how to write clean JavaScript libraries and shows the specific benefits of doing it right. He also reviews JavaScript debugging tools and popular JavaScript toolkits.
  • A whole chapter on making ASP.NET sites that work will with web standards (CSS Friendly Control Adapters, CSS Inheritance, CSS development tools, etc.)
  • Jeff and I wrote a chapter on performance and scaling that I'm pretty happy with. While it talks about some things you might expect, like viewstate management and caching, I also went pretty in-depth into finding out what's slowing your site down and some tips on troubleshooting slow database queries. More on this performance chapter later...
  • Since we're sharing tips on getting the most out of ASP.NET, it only made sense to talk about SubSonic. I wrote 20 pages which explain what SubSonic is, how it can help, and how to use it in your project. I firmly believe that working with SubSonic is one of the best ways to build websites today, and to prepare for data access technologies like Linq in the future (after using the SubSonic query engine and data objects for a while, Linq and Linq To SQL were really easy for me to pick up). This is the first book I know about with any coverage of SubSonic, let alone 20 pages.

Blah, blah. Enough marketing. What about that free stuff you were talking about?

Oh, right. Well, there are three opportunities for you to get a peak at this book for free:

  1. You can read the entire performance chapter Jeff and I wrote online at the SitePoint website. We start with an overview of our philosophy on performance optimization, review tracing (partly to point out why it's important to tune based on real data), look at viewstate and compression optimizations, look at caching, and then go into really gory detail on troubleshooting a slow database.
    Note: while the chapter looks pretty good online, the flow of a website article vs. a book is different in interesting ways. The start of the chapter includes a long code sample with a bunch of trace statements. In the book, you can skim through it without interrupting the flow of the chapter. When I read it online, the long code sample seems to break the chapter flow up a lot more. Fortunately, you can scroll past the code and pretend it never happened.
  2. You can download a PDF with three of the chapters - also for free - from SitePoint. In addition to the performance chapter I just talked about, this includes Chapter 4 (Pushing the Boundaries of the GridView) and Chapter 9 (ASP.NET and Web Standards). That's 156 pages of the book for free. As an added bonus, you can check out how pretty it looks in the full color PDF version. SitePoint sells the book as a black and white book, a color PDF, and as a bundle which includes both. I got spoiled by the color PDF's while proofing, and I really love how they look. These chapters are pretty representative of the rest of the book, so if you're not sure if this is worth your hard earned money I'd really recommend checking out the free PDF download first.
  3. A free copy of the book. Yep, I've got my promo copies, just like Jeff and Phil. Unlike their populist "first come first serve" comment approach, I'm going incredibly pragmatic with my one remaining promo copy. I'm giving one free copy to the person who sells me on why they're the most worthy recipient. Use my blog's contact link and sell, sell, sell me on why I'd be a fool not to give you my last extra copy of this book. Can you write a review somewhere popular, get it in a magazine, use it to construct full size parade float or blimp? Remember, books are for closers. Will I ship internationally? Dunno, sell me on it!
Posted by Jon Galloway | 6 comment(s)
Filed under: , ,

[ASP.NET] Setting the DefaultButton for a Login control

Background

One underused feature in ASP.NET 2.0 is the ability to set a default button for a form. In the past, this often required some extra JavaScript, and was just enough of a pain that it didn't get done until someone asked for it. The ASP.NET form element has DefaultButton and DefaultFocus properties which do exactly what you'd think. For instance, a site I'm working on has a search bar in the header, so I added the following to the form control. Default focus goes to the search textbox, and pressing the enter key submits the form.

<form id="MainForm" runat="server" DefaultButton="Search" DefaultFocus="SearchText">

In my case, the form is declared in my site's MasterPage, You might not want to do that depending on how many forms are in your site; this site's got very few, so it's simpler to set it globally and handle the exceptions. Unless I say different, every page will focus the search textbox and the search button will handle the enter keypress.

Overriding DefaultButton in Page

There are two few ways to override DefaultButton and DefaultFocus. You can override in your codebehind:

this.Form.DefaultButton = MyButton.UniqueID;

In addition to the Form, you can also set the DefaultButton in an <asp:Panel>. That's handy because you can nest Panels, and Panel with the current focus gets to set the Default button, bubbling up until you get to the form.

Gotcha! Containers mangle control ID's, but that's what DefaultButton requires

I ran into this on a simple login form. When the user's logging in, we want to make sure the default button is set to submit the login form, but since the login button is generated by a template, we don't have an ID to work with. The solution is to reference the parent control's id (the <asp:Login> control), then add the $, then add the ID of the actual control. You can find the ID of the control by viewing source if you're not sure.

<asp:LoginView ID="LoginView" runat="server"> <LoggedInTemplate> <div> You are logged in as <asp:LoginName ID="LoginName" runat="server" /> </div> <div> <asp:LoginStatus ID="LoginStatus" runat="server" /> </div> </LoggedInTemplate> <AnonymousTemplate> <asp:Panel ID="panelLogin" runat="server" DefaultButton="Login$LoginButton"> <asp:Login ID="Login" runat="server" /> </asp:Panel> </AnonymousTemplate> </asp:LoginView>

 

 
Posted by Jon Galloway | 34 comment(s)
Filed under:

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!

More Posts