Referred URL
http://www.hanselman.com/blog/BackToBasicsDynamicImageGenerationASPNETControllersRoutingIHttpHandlersAndRunAllManagedModulesForAllRequests.aspx
Often folks want to dynamically generate stuff with ASP.NET. The want to dynamically generate PDFs, GIFs, PNGs, CSVs, and lots more. It's easy to do this, but there's a few things to be aware of if you want to keep things as simple and scalable as possible. You need to think about the whole pipeline as any HTTP request comes in. The goal is to have just the minimum number of things run to do the job effectively and securely, but you also need to think about "who sees the URL and when." This diagram isn't meant to be exhaustive, but rather give a general sense of when things happen. Modules can see any request if they are plugged into the pipeline. There are native modules written in C++ and managed modules written in .NET. Managed modules are run anytime a URL ends up being processed by ASP.NET or if "RAMMFAR" is turned on.
http://www.hanselman.com/blog/BackToBasicsDynamicImageGenerationASPNETControllersRoutingIHttpHandlersAndRunAllManagedModulesForAllRequests.aspx
Often folks want to dynamically generate stuff with ASP.NET. The want to dynamically generate PDFs, GIFs, PNGs, CSVs, and lots more. It's easy to do this, but there's a few things to be aware of if you want to keep things as simple and scalable as possible. You need to think about the whole pipeline as any HTTP request comes in. The goal is to have just the minimum number of things run to do the job effectively and securely, but you also need to think about "who sees the URL and when." This diagram isn't meant to be exhaustive, but rather give a general sense of when things happen. Modules can see any request if they are plugged into the pipeline. There are native modules written in C++ and managed modules written in .NET. Managed modules are run anytime a URL ends up being processed by ASP.NET or if "RAMMFAR" is turned on.
<
system.webServer
>
<
modules
runAllManagedModulesForAllRequests
=
"true"
/>
</
system.webServer
>
You want to avoid having this option turned on if your configuration and architecture can handle it. This does exactly what it says. All managed modules will run for all requests. That means *.* folks. PNGs, PDFs, everything including static files ends up getting seen by ASP.NET and the full pipeline. If you can let IIS handle a request before ASP.NET sees it, that's better.
Remember that the key to scaling is to do as little as possible. You can certainly make a foo.aspx in ASP.NET Web Forms page and have it dynamically generate a graphic, but there's some non-zero amount of overhead involved in the creation of the page and its lifecycle. You can make a MyImageController in ASP.NET MVC but there's some overhead in the Routing that chopped up the URL and decided to route it to the Controller. You can create just an HttpHandler or ashx. The result in all these cases is that an image gets generated but if you can get in and get out as fast as you can it'll be better for everyone. You can route the HttpHandler with ASP.NET Routing or plug it into web.config directly.
Works But...Dynamic Images with RAMMFAR and ASP.NET MVC
A customer wrote me who was using ASP.NET Routing (which is an HttpModule) and a custom routing handler to generate images like this:routes.Add(
new
Route(
"images/mvcproducts/{ProductName}/default.png"
,
new
CustomPNGRouteHandler()));
Then they have a IRouteHandler that just delegates to an HttpHandler anyway:
public
class
CustomPNGRouteHandler : IRouteHandler
{
public
System.Web.IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return
new
CustomPNGHandler(requestContext);
}
}
Note the {ProductName} route data in the route there. The customer wants to be able to put anything in that bit. if I visithttp://localhost:9999/images/mvcproducts/myproductname/default.png I see this image...
Generated from this simple HttpHandler:
public
class
CustomPNGHandler : IHttpHandler
{
public
bool
IsReusable {
get
{
return
false
; } }
protected
RequestContext RequestContext {
get
;
set
; }
public
CustomPNGHandler():
base
(){}
public
CustomPNGHandler(RequestContext requestContext)
{
this
.RequestContext = requestContext;
}
public
void
ProcessRequest(HttpContext context)
{
using
(var rectangleFont =
new
Font(
"Arial"
, 14, FontStyle.Bold))
using
(var bitmap =
new
Bitmap(320, 110, PixelFormat.Format24bppRgb))
using
(var g = Graphics.FromImage(bitmap))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
var backgroundColor = Color.Bisque;
g.Clear(backgroundColor);
g.DrawString(
"This PNG was totally generated"
, rectangleFont, SystemBrushes.WindowText,
new
PointF(10, 40));
context.Response.ContentType =
"image/png"
;
bitmap.Save(context.Response.OutputStream, ImageFormat.Png);
}
}
}
The benefits of using MVC is that handler is integrated into your routing table. The bad thing is that doing this simple thing requires RAMMFAR to be on. Every module sees every request now so you can generate your graphic. Did you want that side effect? The bold is to make you pay attention, not scare you. But you do need to know what changes you're making that might affect the whole application pipeline.
(As an aside, if you're a big site doing dynamic images, you really should have your images on their own cookieless subdomain in the cloud somewhere with lots of caching, but that's another article).So routing to an HttpHandler (or an MVC Controller) is an OK solution but it's worth exploring to see if there's an easier way that would involve fewer moving parts. In this case the they really want the file to have the extension *.png rather than *.aspx (page) or *.ashx (handler) as it they believe it affects their image's SEO in Google Image search.
Better: Custom HttpHandlers
Remember that HttpHandlers are targeted to a specific path, file or wildcard and HttpModules are always watching. Why not use an HttpHandler directly and plug it in at the web.config level and set runAllManagedModulesForAllRequests="false"?<
system.webServer
>
<
handlers
>
<
add
name
=
"pngs"
verb
=
"*"
path
=
"images/handlerproducts/*/default.png"
type
=
"DynamicPNGs.CustomPNGHandler, DynamicPNGs"
preCondition
=
"managedHandler"
/>
</
handlers
>
<
modules
runAllManagedModulesForAllRequests
=
"false"
/>
</
system.webServer
>
Note how I have a * there in part of the URL? Let's try hitting http://localhost:37865/images/handlerproducts/myproductname/default.png. It still works.
This lets us not only completely bypass the managed ASP.NET Routing system but also remove RAMMFAR so fewer modules are involved for other requests. By default, managed modules will only run for requests that ended up mapped to the managed pipeline and that's almost always requests with an extension. You may need to be aware of routing if you have a "greedy route" that might try to get ahold of your URL. You might want an IgnoreRoute. You also need to be aware of modules earlier in the process that have a greedy BeginRequest.
The customer could setup ASP.NET and IIS to route request for *.png to ASP.NET, but why not be as specific as possible so that the minimum number of requests is routed through the managed pipeline? Don't do more work than you need to.
No comments:
Post a Comment