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...
- The Middleware intercepts the current request, an
IUriParser
parses the request looking for commands and creates a dictionary of results. - An
IImageService
is assigned to the request. This will grab the image if required. - An
IImageCache
checks the cache for any previously processed results, if one found, we return the correct response (304, 200) and exit. - Non cached? We process the file using a collection of
IImageWebProcessor
instances using our collected commands via aCommandParser
. - 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.
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.
Cached Response
The result is cached so no request is made to the server.. Nippy!
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!