ImageSharp.Web (My continued plans for world image dominance)

I better build this before someone else does!

I recently read a series of blog posts by Andrew Lock comparing ImageSharp to CoreCompat.System.Drawing in which he creates a simple ASP.NET Core Middleware for resizing image via a URI API. They're really, really great and I suggest you read them.

These posts got me thinking though... I knew I was always going to build a URI API, I just didn't know when I was going to get around to it. I mean... I spend every spare waking hour plus ones I should be sleeping just now building ImageSharp (Big HINT help me finish the bloody thing, it's been TWO YEARS!)

It had to happen sooner rather than later as people are getting twitchy and ImageSharp is getting very close to a beta release, so inspired by Andrew's brilliant writing I got busy...

Introducing ImageSharp.Web

ImageSharp.Web is everything I wished ImageProcessor.Web was and I've only been writing the code for two days. I'm that happy with it. :)

It runs on ASP.NET Core Middleware, is super extensible, and, should be able to support all your web-based imaging needs once finished.

You can see the codebase as of time of writing here

So how does it work?

The Pipeline

The hip bone's connected to the...

  1. The Middleware intercepts the current request, an IUriParser parses the request looking for commands and creates a dictionary of results.
  2. An IImageService is assigned to the request. This will grab the image if required.
  3. An IImageCache checks the cache for any previously processed results, if one found, we return the correct response (304, 200) and exit.
  4. Non cached? We process the file using a collection of IImageWebProcessor instances using our collected commands via a CommandParser.
  5. The resultant image is cached using the IImageCache and we return the result with the correct headers.

Simples!

If all goes well you get something like the following.

Output example for ImageSharp.Web

So lets examine some of the interfaces I just mentioned.

Behind the Curtain

All the interfaces described in the pipeline are injected via the ASP.NET Core Dependency Injection container so you can replace them at will.

IUriParser

This is a neat little interface inspired by some feedback from Andrew that allows you to choose how commands are parsed from the URI.

public interface IUriParser
{
    IDictionary<string, string> ParseUriCommands(HttpContext context);
}

By default ImageSharp.Web will parse querystring parameters but there's nothing to stop you from replacing that with something else. Maybe you want to parse something like /resize/{width}/{height}/{path} who knows?!

IImageService

This interface is very similar to the ones found in ImageProcessor.Web except it's a bit simpler

public interface IImageService
{
    string Key { get; set; }

    IDictionary<string, string> Settings { get; set; }

    Task<bool> IsValidRequestAsync(HttpContext context, ILogger logger, string path);

    Task<byte[]> ResolveImageAsync(HttpContext context, ILogger logger, string path);
}

The key property there indicates an identifier in the URI that we use to choose which service to implement e.g http://mysite.com/remote/http://remote-image.jpg?width=200 would resolve to a RemoteImageService (not built yet) as it uses the remote prefix. Any implementations without a prefix match all requests.

I've put together one so far, the PhysicalFileImageService. You can see the code for that here. It's pretty simple but leverages the DI container to get certain functionality.

IImageCache

I didn't want to build this at first as I wanted to use the built in primitives like IDistributedCache but the API of those primitives was both more complicated than I needed and also lacked the functionality to check if a cached item existed without retrieving it.

public interface IImageCache
{
    IDictionary<string, string> Settings { get; set; }

    Task<CachedBuffer> GetAsync(string key);

    Task<CachedInfo> IsExpiredAsync(string key, DateTime minDateUtc);

    Task<DateTimeOffset> SetAsync(string key, byte[] value, int length);
}

It's again pretty simple but contains enough information to create any kind of cache you require. I've written a PhysicalFileSystemCache implementation so far for non-multi-tenant applications but I plan on creating others for Azure etc. (I will probably charge a small fee for those though as I need to start making some money off the back of my work )

IImageWebProcessor

This is how we actually process the image. We pass our commands and process the image accordingly. There's an overload to Image<TPixel> that loops through available processors.

public interface IImageWebProcessor
{
    IEnumerable<string> Commands { get; }

    Image<TPixel> Process<TPixel>(Image<TPixel> image, ILogger logger, IDictionary<string, string> commands)
        where TPixel : struct, IPixel<TPixel>;
}

Here's the source code for the ResizeWebProcessor. This allows you to choose all the available resize options including the size, mode, and sampler. I'll knock up another couple of processors also that ship out-of-the-box too. AutoRotate, Format & BackgroundColor. Any additional ones will require $$ I'm afraid.

Commands

Parsing input commands is a non-trivial task. The web is a hostile environment and you not only have to deal with potential attacks, you have to deal with developers who are not keen on reading documentation. As such I have had to write a pluggable system of ICommandConverter implementations that are used by the CommandParser singleton to deal with common parsing tasks. Again, this is extensible.

Rich Returns

The Middleware returns the correct response type for processed images (200, 304) and instructs the browser to cache the images for a configurable number of days (we max out by default at 365 days but of course this is configurable)!

As such you will see a server response like the following.

Default Response

The result is processed and returned as normal.

Raw image processing response shown in the network tab

Cached Response

The result is cached so no request is made to the server.. Nippy!

Cached image processing response shown in the network tab

There'll be an event that you can tap into also to add additional headers like Cors headers.

Gimme, Gimme, Gimme!

So when can you get your hands on this?

Well... As soon as the main ImageSharp library is ready for primetime I'll release a Nuget package for ImageSharp.Web. By then any issues will be long ironed out and you'll have a great solution to you image processing needs in .NET Core.

Of course, the sooner you give us a hand, the sooner that can happen ;)

Please comment below, your feedback is super useful!

comments powered by Disqus