ASP.NET Futures - Generating Dynamic Images with HttpHandlers gets Easier
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...
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...
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!
Cool. Have at it at CodePlex. No warranty express or implied.
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.
About Newsletter
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
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.
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.
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?)
"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
@Scott - I left a similar comment to Omer, quoting the docs, but the comment system seems to have eaten it. Just FYI.
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
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.
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."
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. :)
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.