Best practices for security, reliability, and performance

The Six most important best practices

  1. When serving images, use ImageResizer as a pipeline, not as a library. As a pipeline, ImageResizer can handle high loads and disk cache millions of images. When wrapped inside an MVC Action, HttpHandler, or WebAPI action, ImageResizer is unable to integrate with IIS, perform disk caching, support plugins, link to data stores efficiently, or provide advanced HTTP responses. These are fundamental limitations of HttpHandlers compared to HttpModules. Application frameworks like MVC and WebAPI are not designed for large binary payloads, and (really) don't need to be. It's worth investing a couple minutes to locate the right ImageResizer plugin for your data store instead of writing your own MVC action.
  2. Remove the System.Drawing assembly from your project References, when possible. Never use System.Drawing directly from your code. Call void Build(source,destination,settings) instad ofBitmap Build(source, settings). If you have a professional background in C++, the Windows API, imaging, and verify all System.Drawing methods with ILSpy, you can consider using System.Drawing yourself. If not, please allow ImageResizer to handle the entirety of the imaging process. We maintain a (partial) list of the known pitfalls here, and we expose a plugin API that minimizes risk. All System.Drawing classes have memory leaks and threading bugs that have to be manually fixed.
  3. Disk caching is important, and doing it properly requires more than an IF statement. We've seen dozens of companies struggle with memory leaks, race conditions, corrupted images, and permanently locked files because they implemented a naive disk caching system that didn't handle file access, concurrent requests, or thread management properly. We offer a a mature, well-tested DiskCache plugin as part of the Performance Edition. It can handle millions of images and thousands of concurrent requests.
  4. Use a centralized method (like a URL Helper) for filtering all image URLs, even if the method does nothing right now. As you grow, you may need to implement a CDN (or switch CDNs), and this will make it a 30-second task rather than a 30-hour one. Let your centralized method control the data store - just provide it the image ID or relative path and an Instruction instance for the querystring. You might be uploading to disk now, but what if you need to move to blob storage later? Use the ImageResizer.Instructions class or ImageResizer.FluentExtensions library to generate command strings; this will help prevent syntax errors.
  5. Use dynamic resizing instead of pre-resizing your images. If you resize during upload, ensure you store a high-resolution version (2048x1536 at minimum, so it can be viewed full-screen on an iPad). Batch resizing your entire image store is nearly impossible; the only agile and change-friendly option is dynamic resizing.
  6. One image per request. Whether you're uploading or downloading, it's best to have a 1:1 mapping between files and requests, so a corrupted image or network issue causes minimal damage. Avoid batch processing or any in-request parallelization. A single image can briefly require 50-200MB of RAM to process; processing multiple images in a singe request decreases your chance of success. Using multiple cores per image is also counter-productive, as throughput and stability are more important theoretical response times.

File naming best practices

  1. Use a GUID and a whitelisted, lowercase file extension. Example of uploading a resizing with a GUID and whitelisted extension. Using a database counter introduces concurrency issues.
  2. Design for immutability. URLs should always return the same image. This makes edge-caching easy and allows you to enable 'fastMode' on your S3/Azure/SQL readers. Again, don't allow blobs or files to be edited. Create a new blob or file instead, with a different ID or path.
  3. Make sure everything in the path is lowercase. Modern services are a mix of case-insensitive and case-sensitive filesystems and caching systems. Migrating between the two is impossible unless you can globally lowercase all image URLs without breaking things. This means all physical files and folders need to be lowercase. This isn't image-specific advice; humans err, and computer-enforced consistency is a good thing.
  4. Never use the uploaded filename for anything. You can sanitize it and store it in the database for display, but path-safe sanitization requires a whitelist-based approach to characters and extensions. If you allow unsanitized data to enter your real path, you're opening up your site to a variety of easily-executed attacks.

Scaling

  1. Use a load balancer or a managed service like AppHarbor or Azure to ensure you can scale quickly in response to changing traffic conditions. Point your CDN to this instead of a single instance, so you can ramp up easily.
  2. Use Git & GitHub for your code, Web.config, and ImageResizer dlls, so you can auto-deploy to all your instances in seconds.
  3. Use GUIDs for filenames. Don't use custom time or counter-based IDs, or you'll encounter duplicates any time you have simultaneous uploads (this happens very frequently).
  4. Use a true CDN that supports custom-origin servers. See the Cloud Architecture Guide for more info.

ASP.NET Routing, MVC, and Web API best practices

  1. Always install MvcRoutingShim, even if you're only using the Routing framework with WebForms. If it's not installed, two HttpModules will fight over the same request, causing subtle and confusing behavior.
  2. Use the ImageResizer HttpModule for serving images; don't wrap the managed API in an Action or custom HttpHandler. Application frameworks are NOT designed for large binary payloads or serving static files; they're optimized for HTML, XML, and JSON. This is by design; different threading models are needed. IIS is very good at delivering static files, like cached images. ASP.NET MVC, Web API, WCF, Ruby on Rails, and Python/Django are not. All application frameworks suggest using the underlying web server for good static file delivery performance. ImageResizer's HttpModule can talk to IIS, but your Action or HttpHandler executes too late in the request timeline for any transfer of duties to succeed. If you want more details, listen to the Hanselminutes podcast on the topic (feat. Scott Hansleman and I) and read his original article that lead up to it.
  3. Even if you have a custom data store and custom authentication/authorization rules, you can still use the HttpModule (see below).

Integrating with a custom data store

We probably already support your data store.

ImageResizer offers URL-based access to S3 (with S3Reader), MS SQL (with SqlReader), Azure Blob Storage (with AzureReader2), MongoDB GridFS (with MongoReader), arbitrary HTTP-accessible files (through RemoteReader), physical files, or virtual files.

If you're acessing files on a SAN or network share, there are a few bugs in ASP.NET and IIS you should be aware of.

If you need to access files from some other kind of data store, you'll need to make a plugin with 4 methods. It's not hard to make your own image provider, and it will allow disk caching to work properly.

Authentication and authorization for private images

ImageResizer respects all Url Authorization rules and all ASP.NET Authentication mechanisms.

Remember that ImageResizer is non-invasive; it doesn't touch requests unless they have image commands.

This means that it's generally more appropriate to put authorization logic into the ASP.NET Application-level AuthorizeRequest event. ImageResizer offers a Pipeline.AuthorizeImage event, but it is not applied to requests outside ImageResizer's scope. Feel free, however, to call your authentication logic routing from both events. ImageResizer's Pipeline.AuthorizeImage occurs after all URL rewriting, which may simplify authorization a bit.

You can also decide to implement authorization at the data store level; SqlReader has a dedicated event that is applied to all requests through that provider, whether ImageResizer is involved or not.

There is currently no way to use a CDN to cache private content.

Customizing the URL syntax

ImageResizer offers more granular control over the URL syntax than what ASP.NET Routing provides. As you remember, ASP.NET Routing occurs too late during the request lifetime to permit efficient delegation back to IIS.

Here's an example Rewrite handler that forces all images within the "~/folder/" to be resized to a width of 100 pixels. Pretty straightforward.

Config.Current.Pipeline.Rewrite += delegate(IHttpModule sender, HttpContext context, IUrlEventArgs ev) {
  if (ev.VirtualPath.StartsWith(VirtualPathUtility.ToAbsolute("~/folder/"), StringComparison.OrdinalIgnoreCase))
      ev.QueryString["width"] = "100";
};

When in doubt, consult the list of plugins or Google this site.

We've made a plugin for nearly every situation, and they're easy to make yourself as well.

If all else fails, try browsing the docs or searching the site.