Scott Hanselman

ASP.NET Futures - Generating Dynamic Images with HttpHandlers gets Easier

August 22, 2008 Comment on this post [24] Posted in ASP.NET | ASP.NET Dynamic Data | ASP.NET MVC
Sponsored By

There's a treasure trove of interesting stuff over at http://www.codeplex.com/aspnet. It's a bunch of potential future stuff that the ASP.NET team is working on. That's where you can get builds of ASP.NET MVC, Dynamic Data Futures, and new AJAX stuff.

Two days ago a new CodePlex release snuck up on the site. It's a potential new feature, so it could go nowhere, or we could make T-shirts and sing its praises. Could go either way. No promises.

Justin Beck, an ASP.NET intern, prototyped and design it, then Marcin Dobosz, an ASP.NET Developer tidied it up nicely. Right now it's called ASP.NET Generated Image and you can get a release today.

Why should you care?

I've done a lot of HttpHandlers that generate images. It's usually pretty tedious. When I was working banking, I wrote an example HttpHandler that would take two Check Images (back and front) and composite them into a single image on the server side, then serving up the composite. Usually you're messing around in with MemoryStreams and Images, and then you serialize the result out to the Response.OutputStream, making sure the MIME Types are set appropriately. If you're really clever, you'll remember to do some client-side and appropriate caching, but I rarely see that in the wild.

So what have Justin and Marcin done here?

First, they've created a control with a little design mode "chrome" that makes getting started easier. The control is actually an extension of <asp:image/> that creates an HttpHandler and wires up the the src= attribute to the handler. If that were all, it'd be cute. However, second, and most importantly, they've created a base class that can do caching, transformations and parameter passing for you. It's really nicely factored, IMHO.

Here's a simple example. Put one of these GeneratedImage Controls on the page, and click on it...

Screenshot of the Context Menu of the GeneratedImage Control 

Next, click "Create Image Handler." You'll get this in the markup, and a new file in the Project:

public class ImageHandler1 : ImageHandler {

public ImageHandler1() {
// Set caching settings and add image transformations here
// EnableServerCache = true;
}

public override ImageInfo GenerateImage(NameValueCollection parameters) {
// Add image generation logic here and return an instance of ImageInfo
throw new NotImplementedException();
}
}

All I need to do now is override GenerateImage. Any parameters to the control will be in the NameValueCollection. I just need to return a new ImageInfo, and the constructor for ImageInfo can take either an Image, a byte[] or an HttpStatusCode. Clever.

Here, I'll add some text:

<cc1:GeneratedImage ID="GeneratedImage1"
runat="server" ImageHandlerUrl="~/TextImageHandler.ashx" >
<Parameters>
<cc1:ImageParameter Name="Hello" Value="text in an image" />
</Parameters>
</cc1:GeneratedImage>

As I said, that parameter is passed in, and it come from <% %> code or DataBinding or whatever. It doesn't care. Now, I'll draw on an image:

public class TextImageHandler : ImageHandler {

public TextImageHandler() {
this.ContentType = System.Drawing.Imaging.ImageFormat.Png;
}

public override ImageInfo GenerateImage(NameValueCollection parameters) {
// Add image generation logic here and return an instance of ImageInfo
Bitmap bit = new Bitmap(300, 60);
Graphics gra = Graphics.FromImage(bit);
gra.Clear(Color.AliceBlue);
gra.DrawString(parameters["Hello"], new Font(FontFamily.GenericSansSerif, 16), Brushes.Black, 0, 0);
return new ImageInfo(bit);
}
}

Which results in...

Picture of a dynamic generated image with text inside it 

Slick. What else can I do easily? Let's right-click on the ImageHandler base class and see what it looks like:

namespace Microsoft.Web
{
public abstract class ImageHandler : IHttpHandler
{
protected ImageHandler();

public TimeSpan ClientCacheExpiration { get; set; }
public ImageFormat ContentType { get; set; }
public bool EnableClientCache { get; set; }
public bool EnableServerCache { get; set; }
protected List<ImageTransform> ImageTransforms { get; }
public virtual bool IsReusable { get; }

public abstract ImageInfo GenerateImage(NameValueCollection parameters);
public void ProcessRequest(HttpContext context);
}
}

ImageTransforms? Hm...you can also setup a collection of ImageTransform objects into a little mini-image processing pipeline to do whatever you like.

Here we add a copyright watermark dynamically. The Transform is added in the constructor in this example.

public class TestCustomImages : ImageHandler {

public TestCustomImages()
{
this.ImageTransforms.Add(new CustomImageTransforms.ImageCopyrightTransform { Text = "Copyright Me! 2008" });
this.ContentType = System.Drawing.Imaging.ImageFormat.Png;
}

public override ImageInfo GenerateImage(NameValueCollection parameters) {
Bitmap pic = new Bitmap(200, 50);
Graphics gra = Graphics.FromImage(pic);
gra.Clear(Color.SkyBlue);
return new ImageInfo(pic);
}
}

The source for the CustomTransform, and for all the samples and the preview assembly that makes it all work is up on CodePlex now. If you've got ideas, find bugs, or think it's cool, leave a comment here and I'll make sure they get them. You can also leave bugs in the Issue Tracker on CodePlex.

Here's what the roadmap document says they've got planned...

  • Add Item Templates for an Image Handler
  • Providing parameter type inference for the handler instead of a Name Value Collection.
  • Giving you control of transforms, caching inside of your Generate Image Method.

Does it work with ASP.NET MVC?

Gee, I dunno. Let's see. Remember that if you're using ASP.NET MVC with the WebFormsViewEngine, as long as there's no Postback, some Server Controls can still work. This just might, as it renders on the first load.

I added the ASHX handler file to my MVC project, referenced the DLL, made sure to register the tag prefix and it mostly worked. The design service isn't working, saying something about "could not be set on property ImageHandlerUrl," but I'm encourage I got this far. Let's encourage them to keep MVC in mind!

image

Cool. Have at it at CodePlex. No warranty express or implied.

Technorati Tags: ,

About Scott

Scott Hanselman is a former professor, former Chief Architect in finance, now speaker, consultant, father, diabetic, and Microsoft employee. He is a failed stand-up comic, a cornrower, and a book author.

facebook twitter subscribe
About   Newsletter
Hosting By
Hosted in an Azure App Service
August 22, 2008 3:10
You should dispose the IDisposable Graphic object in the example above. Since ImageInfo takes a bitmap in the constructor, I assume it takes ownership of the Bitmap object your created so I wouln't worry about that one, but Graphic certainly would need to be used with a using() statement AFAIK.
August 22, 2008 3:18
Robert - True. It'll eventually get cleaned up, but you're correct, it's best to be tidy.
August 22, 2008 3:20
Is someone going to let them know that technically, using GDI+ on a web server is not supported?
August 22, 2008 3:38
I've done my fair share of GDI+ programming in our project, and I think I'm going to go for less than more.

Although it is very cool to do, we have found many issues by doing it.

Even though, we do caching and what have you, we have had problems with performance, issues with accessibility, and problems with our integrators... and all because a web designer thought it would be cool to use a non-standard font everywhere on our website.

http://tinyurl.com/6ym4ab
August 22, 2008 3:53
That's surprising. I've generated Charts and Graphs for banking for *years* and never had as much as a hiccup, even on incredibly large sites. This is literally the first time I've heard this.
August 22, 2008 4:34
I did a blog post (with the source code) with pretty similar features, but it seems to be a pretty nice library. Basically a small class library to open images, do manipulations on them with parameters and then cache the result depending on all those parameters.

http://blog.lavablast.com/post/2008/07/Image-Thumbnail-Caching.aspx

This is something we all have to do at one point in ASP.NET, let's hope this project gets popular.
August 22, 2008 8:24
Like Hanselman said above I've written dating sites and social networking sites that use plenty of GDI+ for scaling images for profiles and have never had a hiccup. The only issues I've ever had were not around GDI+ but instead trying to handle large uploads when people upload HUGE files, but that is another problem and a different discussion.
August 22, 2008 9:57
Is this one of those things where it's probably OK to use GDI+ on a Web server but not officially supported because Microsoft hasn't thoroughly tested those scenarios? Does this new ASP.NET Dynamic Image Handler mean that Microsoft will now officially support using the GDI+ API's on Web servers?

I suspect that most of those map to the native Win32 API's and graphics API's require large memory buffers which means memory will get pinned to perform native ops, which means that the garbage collector might have to do more work to collect these, which means that the garbage collector won't be able to collect these as quickly if you aren't fastidious about disposing your objects (*cough* Scott! *cough*), etc.
August 22, 2008 11:31
Where are you guys seeing or hearing that it's not supported to do this kind of drawing? I'll ask around and get a boss to say otherwise. I've never given it a second thought.
August 22, 2008 12:31
Does anyone remember the asp:DynamicImage control that was in ASP.NET 2.0 beta but got cancelled? Scott, could you ask around why it was cancelled, if it was because of time why it wasn't in ASP.NET 3.5?

If it was because of technical difficulties, I think those original developers should contact Justin Beck and share code/ideas etc. One thing I remember is that it had a handler with the .axd extension. (What is that anyway?)
August 22, 2008 16:14
@Kevin: Holy crap, is that Yi Ar Kung Fu in your avatar? Brings back sweet, sweet memories.
August 22, 2008 16:45
I was actually surprised to see this, since I also knew this was unsupported. Knowing this, what I do in my ASP.NET application is simply create the images beforehand in a process outside the hosting IIS process and save them in the database (being few and under 1KB, there's no real impact there).

"Classes within the System.Drawing.Imaging namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions."
Source
August 22, 2008 20:52
@Kent - Yes, yes it is. :)

@Scott - I left a similar comment to Omer, quoting the docs, but the comment system seems to have eaten it. Just FYI.
August 22, 2008 21:17
I'm curious, what's the technical advantage of achieving this using http handlers rather than just using a .aspx page as the image source and injecting the response stream from there? Is it purely that the "webform" is a heavier base class?
Ian
August 22, 2008 21:20
Ian - Yes, a page that generates an image is overkill. Remember that a Page *is* an HttpHandler, except it has a whole Page lifecycle, a bunch of events, the control tree and a whole pile of totally not related stuff. HttpHandlers for specialization will always be faster than making the Page dance.
August 22, 2008 21:36
@Mike- Exactly! That's just the thought that came to my mind. This seems like a very similar control to what DynamicImage was supposed to become back in ASP.NET 2.0. That was one of the cut 2.0 features I was most sad to see not make it out of beta.

That said, I'm happy to see the idea re-kindled. Hopefully this will find it's way in to ASP.NET "4.0."

-Todd
August 22, 2008 22:43
@Mike The DynamicImage support that was in .NET 2.0 Beta 1 was cut because it had a dependency on another feature that was cut. There was a feature called DiskOutputCaching which would allow you to do OutputCaching but have it cached to disk instead of memory. The DynamicImage depended on this support for its caching. Performence testing revealed that DiskOutputCaching was slower then not caching and it was too late in the cycle to try and fix the feature. When it got pulled out everything that depended on it including DynamicImage was pulled as well. It did not show up in 3.5 because of red bit/green bits (red bits were core .NET and could not be changed) (green bits are new additive changes), which we were only really allowed to make additive changes and not core level changes (because 3.5 depends on 2.0) we were unable to change things like the low level caching. In .NET 4.0 we are allowed to make core level changes and plan on making the ASP.NET caching mechansim provider based meaning you can plug in another caching provider for example something like memcache. That is sort of the history of things. :-)
August 22, 2008 23:39
@ScottHu - (OK. No more Scotts allowed at Microsoft in DevDiv - with ScottGu, ScottHa, ScottGa and now I find there is a ScottHu. My mind is about to explode.) Back to the real issue at hand. Is there any timeline on when we'll start to see some of the early caching bits? We make heavy use of caching and caching providers in DotNetNuke and have already started investigation into a Velocity caching provider. It would be nice not to waste a bunch of effort if we might end up refactoring our caching infrastructure to use the .Net 4.0 providers.

As for Dynamic images, Dino Esposito actually had an MSDN Article in 2004 that backported the Image Generation Service to Asp.Net 1.1. It would be interesting to see how this differs from the Futures work.
August 23, 2008 0:51
Test post by Steve S.
August 23, 2008 7:46
@Scott - How do you not know that Drawing is not 'supported' in ASP.Net and work at Microsoft? ;)

When doing support at DD I found that if a user opened up an support issue with M$ and it took place in ASP.Net, that if they found a reference to System.Drawing.dll they would stop the support ticket since it was 'not supported'. It obviously does work though.

http://msdn.microsoft.com/en-us/library/system.drawing.aspx

Quoted "Classes within the System.Drawing namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions."
August 23, 2008 8:48
Tommy - Heh, I've still not worked at Microsoft a year. ;) I worked at my last employer for nearly 7 years, most of those while happily generating charts and graphs on the server-side while under great load. ;)
August 23, 2008 22:47
hi there,

just tried this on my machine, i just wanted to note that when using this control with a VB .NET Website
the generated handler will be created using C#.




---- almost hitting the 30K mark on the RSS feed Readers, Congrats, you deserve it. :)
August 25, 2008 3:20
I wrote a control that does image rendering and is "databindable", mostly because I got tired of rewriting the same code over and over again. It is posted on codplex for quite a while. It renders text, normal image or images from a database, but it does not handle caching. The source is at http://www.codeplex.com/dbic if someone is interested.
October 06, 2008 14:00
OK stop the nonsense. Generated images in websites is just a must have. Microsoft do support them because they use them a lot themself.

If we listen all the nonsense there, we should stop doing charts on websites, close map/traffic sites, and get back to static HTML 1.1 sites.

Thx Scott, for the info, wasn't aware there is a wizard made by MS to shotcut the handler declaration.

Greetings for France.

Comments are closed.

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.