Views: 1129
Number of votes: 8
Average rating:

Episerver and ImageProcessor: more choice for developers and designers

When working with images in Episerver from a developer/designer perspective, the go-to solution seems to be to install ImageResizer and the accompanying ImageResizer plugin package (package, source) from Valdis (which are both excellent). This does however come with some drawbacks:

  • If you want to use a disk based cache for your resized images (and you REALLY should on a production environment), you need to buy a commercial license for ImageResizer.
    If you don't have a license and still use the DiskCache plugin, you'll see a red dot in the lower right corner of every processed image.
  • You might need to buy commercial licenses for extra modules/plugins.

With this blogpost I would like to introduce an alternative solution. You may have heard of the (brilliant) ImageProcessor package by the all round nice guy James Jackson-South. It is used in many, many projects, websites and software packages, is completely open source, is very configurable and does not require paid-for licenses. To make it work seamlessly with Episerver requires some code though. The package I have built and introduce here contains those changes and additions. It is called ImageProcessor.Web.Episerver (because it is technically an ImageProcessor extension) and will be is available from the Episerver NuGet feed shortly (after approval).

Let me first sum up what is in the package and then explain in more detail:

  • It has code so ImageProcessor can find and process images from blob storage
  • It has a disk cache which stores the processed images alongside the originals in the blob storage
  • It comes with a strongly typed Fluent API for all ImageProcessor.Web image processing methods

Let ImageProcessor read from blob storage

Episerver uses blob storage to store media items like images and documents. ImageProcessor needs some help to understand where to get the files from. Specifically, for that goal ImageProcessor comes with an IImageService. From the docs: “The IImageService defines methods and properties which allow developers to extend ImageProcessor to retrieve images from alternate locations to process.” An implementation of this interface for Episerver is quite straightforward and has already been written (see for example here and here). I added a combination of these in this package. I also added code to make the processing work from within the editing experience. Just like with the solution from Valdis, this might not always play well with unpublished images.

Caching

ImageProcessor uses a disk based caching mechanism that creates a file structure in /App_Data/cache by default (the location it stores the files is configurable). Using it this way is certainly possible, but then you end up with two separate structures of files. I thought it would be nicer (not necessarily better) to store the processed and cached files alongside the original blobs. This package contains code which does just that. This is possible because ImageProcessor exposes an IImageCache interface. From the docs: “The IImageCache defines methods and properties which allow developers to extend ImageProcessor to persist cached images in alternate locations.” It retains all the good stuff that the default ImageProcessor implementation already has, like automatic cleanup and store cache outside of www root. Yes, it can also be an UNC path. To change the path, follow the regular steps to do this in Episerver (in <episerver.framework> section change <appData basePath>).

Fluent API

ImageProcessor, like the name implies, does a lot more than just resizing images. See the ImageProcessingModule documentation for all available out of the box methods that you can use. It works as an HttpModule that processes commands added to the query string of an image in your views. Adding to the query string is however not very developer friendly. You don't get IntelliSense and there is no compile time checking of the syntax. Inspired by the fluent api in Valdis’s package, I decided to add this to my package as well. I added a complete, strongly typed, fluent implementation for all default ImageProcessingModule methods and provided a couple of extra ones as shortcuts. This means that you can now do things like this in your views:

@Html.ProcessImage(Model.Image).Resize(375,null).ReplaceColor("fff", "f00",99).Watermark("Episerver",new Point(100,100),"fff")

Note that the Resize function has parameters that are only used in specific combinations. I created a couple of overloads for this. Using the mentioned shortcuts, instead of .Resize you could also use .Width.

Don't forget to add the using stament to your view:

@using ImageProcessor.Web.Episerver

I tried to retain the defaults for parameters as much as possible. I documented the methods and parameters based on the original documentation. IntelliSense is your friend and will show you all the information when needed.

Note that the commands will be executed in the order supplied.

To get an example after installing the package in the Alloy starter kit, change TeaserBlok.cshtml in Views/Shared/Blocks to the code below:

@using EPiServer.Core
@using ImageProcessorEpiserverTest.Controllers
@using ImageProcessor.Web.Episerver
@using System.Drawing
@model TeaserBlock
<div class="border">
    @*Link the teaser block only if a link has been set and not displayed in preview*@
    @using (Html.BeginConditionalLink(
                !ContentReference.IsNullOrEmpty(Model.Link) && !(ViewContext.Controller is PreviewController),
                Url.PageLinkUrl(Model.Link),
                Model.Heading))
    {
        <h2 @Html.EditAttributes(x => x.Heading)>@Model.Heading</h2>
        <p @Html.EditAttributes(x => x.Text)>@Model.Text</p>
        <div @Html.EditAttributes(x => x.Image)>
            <img src="@Html.ProcessImage(Model.Image).Resize(375,null).ReplaceColor("fff", "f00",99).Watermark("Episerver",new Point(100,100),"fff").RoundedCorners(20)" />

        </div>
    }

</div>

Remenber to change the @using in line 2 AND to enable all the necessary processors in config/imageprocessor/processing.config

This should give you the following:

Image Capture.PNG

Installation

The package is available on the Episerver NuGet feed with the name ImageProcessor.Web.Episerver. It obviously has a dependency on both the ImageProcessor core and the ImageProcessor.Web packages and will therefore add these to your solution.

ImageProcessor uses sensible defaults for it's configuration options. If you would like to override these defaults, you can install the ImageProcessor.Web.Config package. We need to do this here, so this package is also installed as a dependency. The needed configuration changes to both the ImageProcessor.Web configuration and the site web.config are then made by my NuGet package.

If you want to enable or disable processors, you can do that in the standard ImageProcessor way. See http://imageprocessor.org/imageprocessor-web/configuration/#processingconfig how to do this. If you want to tryout the processors, you can copy the contents from this file in the sample site in the repository to enable them all.

To minimize possible attack areas, only enable what you need in your production environment!

Uninstalling the package will revert everything back to the default ImageProcessor configuration.

Next steps

ImageProcessor has plugins to store the cache in Azure or Amazon S3 blob storage. I hope to release Episerver adapted versions of these plugins in the near future. The goal is that cache will be stored alongside the source blobs just as with the disk based solution without requiring additional configuration besides the regular Episerver blob storage cloud settings.

James is working on a completely rebuilt .NET Core version of ImageProcessor named ImageSharp (in beta at the time of writing). When this gets released, ImageProcessor will probably go into maintenance mode. He mentioned on Twitter that he wants to write a HttpModule which, because of .NET Standard support, makes it possible to use ImageSharp from .NET Framework and Episerver. I hope to be able to offer a comparable package then. Lets first see how this goes…

All the code for this package is on my GitHub account. I would really appreciate feedback in the comments below and help in the form of issues and, even better, pull requests in the repository.

Hope this is helpful.

  Please login to post a comment