Episerver Adaptive Images add-on
Developer documentation
- Introduction
- Installing
- Adding adaptive image properties
- Rendering adaptive images
- PropertyList, or IList<T>, properties
- Content Delivery API support
- Migrating Episerver image properties
- Configuration and settings
- Image providers
- Security
- Local development
See the web editor documentation on how to use the add-on in edit mode.
Introduction
This add-on makes it easy to add a new type of image property to content types, with a user-friendly user interface for web editors.
Developers are able to easily define image constraints, such as minimum size and proportions, and web editors can customize images for different screen sizes without risk of violating those restrictions.
Images can reside in Episerver or in external systems for digital asset management (DAM).
End-user images are automatically optimized, cropped, and rescaled, and served through a CDN.
Note: This is optional for local development, but production/high-load environments should always use a CDN for image transformation and delivery.
Installation
- Add NuGet package TedGustaf.Episerver.AdaptiveImages to your Episerver website
- For TinyMCE (v2) support, add the Adaptive Images plugin to the configuration of applicable editor instances (optional):
config.Default().AddEpiserverSupport().AddPlugin(AddonTinyMceSettings.PluginName);
- Restart the website
- You may need to "Reset Views" among the user settings to make the "Image bank" component visible
Add an adaptive image property
public virtual AdaptiveImage HomePageHero { get; set; }
Set minimum image size
[Size(1024, 768, FormFactor.Small | FormFactor.Medium)] // Mobile and tablet images must be at least 1024x768 pixels
[Size(1920, 1080, FormFactor.Large)] // Desktop image must be at least 1920x1080 pixels
public virtual AdaptiveImage HomePageHero { get; set; }
Set proportion constraints
[Proportions(16, 9, FormFactor.Large)] // Desktop image should have widescreen proportions
[Proportions(1, 1, FormFactor.Small | FormFactor.Medium)] // Square images for mobile and tablet
public virtual AdaptiveImage HomePageHero { get; set; }
Combining constraints
When a web editor selects or crops images they're validated against these constraints:
[Proportions(16, 9, FormFactor.Large)] // Widescreen proportions
[Size(1920, FormFactor = FormFactor.Large)] // Minimum width of 1920 pixels (height determined by proportions constraint)
public virtual AdaptiveImage HomePageHero { get; set; }
Customize form factor display names
The default display names are "Desktop", "Tablet", and "Mobile" for the Large, Medium, and Small form factors, respectively.
You can customize form factor display names on a per-property basis by using the DisplayNames attribute:
[DisplayNames(Large = "Widescreen", Medium = "Standard", Small = "Square")]
public virtual AdaptiveImage MyAdaptiveImage { get; set; }
To localize form factor names, you can also use localization keys:
[DisplayNames(Large = "/path/to/translation/for/large")]
public virtual AdaptiveImage MyAdaptiveImage { get; set; }
You can also change and/or translate form factor display names site-wide by adding appropriate translations, for example through an XML language file like the following:
<?xml version="1.0" encoding="utf-8" ?>
<languages>
<language name="English" id="en">
<ContentTypes>
<AdaptiveImage>
<properties>
<Large>
<caption>Desktop</caption>
</Large>
<Medium>
<caption>Tablet</caption>
</Medium>
<Small>
<caption>Mobile</caption>
</Small>
</properties>
</AdaptiveImage>
</ContentTypes>
</language>
</languages>
Add a single image property
public virtual SingleImage OpenGraphImage { get; set; }
Setting constraints
For single images, you cannot specify form factor for the Size or Proportions attributes. You may also only include one of each attribute:
[Size(1920)]
[Proportions(16, 9)]
public virtual SingleImage OpenGraphImage { get; set; }
Rendering adaptive images
Default rendering
This will render the image(s) using default breakpoints, automatically cropped according to any constraints:
@Html.PropertyFor(x => x.HomePageHero)
See the Configuration section on how to change the default breakpoints and other settings.
Override breakpoints, image widths, and other default rendering settings
You can specify desired image widths and/or breakpoints, if different from the defaults:
@Html.PropertyFor(m => m.HomePageHero, new {
largeBreakpoint = 1280, // Desktop breakpoint
mediumBreakpoint = 768, // Tablet breakpoint (767 and below is considered mobile)
largeWidth = 1920, // Desktop image width
mediumWidth = 1279, // Tablet image width
smallWidth = 500, // Mobile image width
ignoreSizeConstraints = true, // Do not render according to size constraints by default (not applicable when widths are specified)
altText = "An adaptive image", // Overrides any alt text specified by web editor
cssClass = "hero-image", // CSS class applied to the picture element
lazyLoad = true, // Image should be lazy-loaded by browser
quality = 75, // Use explicit quality value (0-100) for all form factors (default is auto)
smallQuality = 100 // Use maximum quality for the small form factor only
})
Apply image transformations
For other custom rendering scenarios, apply transformations using a fluent syntax:
// Get the large ("desktop") image, cropped according to constraints and resized to a width of 1280 pixels
var largeImage = currentContent.GetImageRenderSettings(x => x.HomePageHero.Large)
.ResizeToWidth(1280);
// Create a 300x300 square version of the medium ("tablet") image, overriding any proportions constraints
var squareImage = currentContent.GetImageRenderSettings(x => x.HomePageHero.Medium)
.Crop(new Proportions(1, 1))
.ResizeToWidth(300);
Note: When retrieving render settings for an AdaptiveImage form factor, the expression needs to include the parent AdaptiveImage object in order to be able to resolve any constraints. Otherwise an exception will be thrown.
You can also create an ImageRenderSettings instance manually to circument any image constraints, for example to render an image with its original proportions, regardless of constraints:
// Create a 300px wide image with its original proportions preserved, ignoring any proportions constraints
var imageWithoutConstraints = new ImageRenderSettings(currentContent.HomePageHero.Large).ResizeToWidth(300);
Render a specific image
You can render an <img> element based on above render settings:
@Html.RenderSingleImage(squareImage)
Get image URLs
If you need to get the final URL of an image, for example to set a background image, first apply any transformations and then call GetUrl():
@{
// Get the large ("desktop") image scaled to a width of 1920 pixels
var backgroundImageUrl = startPage.GetImageRenderSettings(x => x.HomePageHero.Large)
.ResizeToWidth(1920)
.GetUrl();
}
<div style="background-image: url(@backgroundImageUrl)"></div>
Resolving property image constraints and settings
When creating a view with a model type of AdaptiveImage, size and proportion constraints are not accessible as they're defined through attributes on the parent content, i.e. block or page, instance. However, constraints and crop settings can be retrieved like:
// "currentContent" below is a page or block instance with an AdaptiveImage property:
// Get all applicable constraints for all form factors
var imageConstraints = currentContent.GetImageConstraints(x => x.HomePageHero);
// Get crop settings for the large, i.e. "desktop", image
var largeImageCropSettings = currentContent.GetCropSettings(x => x.HomePageHero.Large);
// Get proportions constraints for the medium, i.e. "tablet", image
var mediumImageProportionsConstraints = currentContent.GetProportionsConstraint(m => m.HomePageHero.Medium);
// Get size constraints for the small, i.e. "mobile", image
var smallImageSizeConstraints = currentContent.GetSizeConstraint(m => m.HomePageHero.Small);
Make images required
To make a property required, use the RequiredImage property:
[RequiredImage]
public virtual AdaptiveImage Logotype { get; set; }
To only make specific form factors required, specify the FormFactor attribute property (not applicable for SingleImage properties):
[RequiredImage(FormFactor = FormFactor.Large | FormFactor.Medium)]
public virtual AdaptiveImage Logotype { get; set; }
To make the image description (i.e. alt text) required, you set the AlternateText property on the attribute:
[RequiredImage(AlternateText = true)]
public virtual AdaptiveImage Logotype { get; set; }
Make images localizable
To make a property culture-specific, use the CultureSpecificImage property:
[CultureSpecificImage]
public virtual AdaptiveImage Banner { get; set; }
You can choose to only make images or alternate text localizable by specifying the Images and/or AlternateText attribute properties. For example to use the same images for all languages but be able to localize the alternate text:
[CultureSpecificImage(Images = false, AlternateText = true)]
public virtual AdaptiveImage Banner { get; set; }
Rendering single images
Default rendering
This will render a SingleImage property, automatically cropped according to any constraints:
@Html.PropertyFor(x => x.OpenGraphImage)
You can optionally specify desired image width and CSS classes:
@Html.PropertyFor(m => m.OpenGraphImage, new {
width = 1920,
cssClass = "og-image" // CSS class applied to the img element
altText = "A single image", // Overrides any alt text specified by web editor
})
PropertyList, or IList<T>, properties
There are a few simple steps to follow to implement PropertyList properties where the item type contains one or more image properties.
1. Create your item type
If your item type should have just one image property, you can inherit AdaptiveImagePropertyListItem or SingleImagePropertyListItem.
You can then override the Image property, for example to apply size and/or proportions constraint attributes.
If you prefer to create your own POCO type from scratch, you need to ensure you add JSON attributes like so:
public class MyListItem
{
// Adaptive image property
[JsonProperty]
[JsonConverter(typeof(AdaptiveImageConverter))]
public virtual AdaptiveImage MyAdaptiveImage { get; set; }
// Single image property
[JsonProperty]
[JsonConverter(typeof(SingleImageConverter))]
public virtual SingleImage MySingleImage { get; set; }
}
2. Create IList<T> property definition
[PropertyDefinitionTypePlugIn]
public class MyListProperty : ImagePropertyList<MyListItem>
{
}
3. Create an editor descriptor for the property type
[EditorDescriptorRegistration(TargetType = typeof(IList<MyListItem>), EditorDescriptorBehavior = EditorDescriptorBehavior.OverrideDefault)]
public class MyListItemEditorDescriptor : ImagePropertyListEditorDescriptor<MyListItem>
{
}
4. Add property to your content type
public virtual IList<MyListItem> Items { get; set; }
Migrating Episerver image properties
If you're adding the Adaptive Images add-on to an existing Episerver website you may want to migrate existing image properties to AdaptiveImage or SingleImage properties.
One way to do this is to create a SingleImage instance by passing a ContentReference to the constructor:
// Content reference to ImageData content with ID 123
var contentReference = new ContentReference(123);
var episerverImage = new SingleImage(contentReference);
// Assign to SingleImage property
episerverImage.AssignTo(currentPage.MySingleImage);
// Assign to AdaptiveImage property
episerverImage.AssignTo(currentPage.MyAdaptiveImage);
Note: When saving the content, you may need to use SaveAction.SkipValidation if your image properties have size or proportions constraint attributes which the original Episerver image violates.
If you do skip validation, you may want to consider setting AddonSettings.LaxValidationOfPublishedImages = true to treat validation errors as warnings for unmodified image properties.
Content Delivery API support
This add-on is designed to work out-of-the-box with Episerver's Content Delivery API.
However, you may want to include some custom serialization to include constraints data in the API payload.
You'll need one custom PropertyModel for AdaptiveImage properties and one for SingleImage ones to get ImageConstraints included in the serialized data from the API:
Note: The code below is a proof-of-concept and is merely intended as guidance.
public class AdaptiveImageConverter : PropertyModel<AdaptiveImage, PropertyBlock<AdaptiveImage>>
{
public ImageConstraints ImageConstraints { get; set; }
private IContentLoader _contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
public AdaptiveImageConverter(PropertyBlock<AdaptiveImage> adaptiveImage) : base(adaptiveImage)
{
if (adaptiveImage.Block.IsSet())
{
var parentLink = adaptiveImage.Parent["ContentLink"]?.Value ?? adaptiveImage.Parent["PageLink"]?.Value;
if (parentLink is ContentReference parentContentLink)
{
var parentContent = _contentLoader.Get<IContent>(parentContentLink);
ImageConstraints = parentContent.GetImageConstraints(adaptiveImage.Name);
}
}
}
}
public class SingleImagePropertyModel : PropertyModel<SingleImage, PropertyBlock<SingleImage>>
{
public ImageConstraints ImageConstraints { get; set; }
private IContentLoader _contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
public SingleImagePropertyModel(PropertyBlock<SingleImage> singleImage) : base(singleImage)
{
if (singleImage.Block.IsSet())
{
var parentLink = singleImage.Parent["ContentLink"]?.Value ?? singleImage.Parent["PageLink"]?.Value;
if (parentLink is ContentReference parentContentLink)
{
var parentContent = _contentLoader.Get<IContent>(parentContentLink);
ImageConstraints = parentContent.GetImageConstraints(singleImage.Name);
}
}
}
}
Configuration and settings
Add-on settings
Global add-on settings, such as default breakpoints, are configured through the static AddonSettings class:
// Default breakpoints unless specified through rendering arguments
AddonSettings.LargeBreakpoint = 1200;
AddonSettings.MediumBreakpoint = 800;
// Default rendering widths unless specified through rendering arguments (defaults to breakpoint widths if not specified)
AddonSettings.LargeWidth = 1170;
AddonSettings.MediumWidth = 940;
AddonSettings.SmallWidth = 727;
// The maximum rendering size allowed regardless of rendering arguments
AddonSettings.MaximumWidth = 1920;
Note: If using very large original images, it might be sensible to reduce the maximum image size rendered in the crop dialog to improve UI performance. This will not affect the actual rendering on the website:AddonSettings.MaxCropDialogImageWidth = 2000;
TinyMCE settings
TinyMCE settings, such as behavior when standard images are inserted, are configured through the static AddonTinyMceSettings class:
// Use custom block type for wrapping adaptive images in TinyMCE
AddonTinyMceSettings.BlockContentTypeId = contentTypeRepository.Load(typeof(CustomTinyMceAdaptiveImageBlock)).ID;
// Specify behavior when image is drag-and-dropped to TinyMCE
AddonTinyMceSettings.ImageConversionBehavior = ImageConversionBehavior.AlwaysConvert;
Note: If specifying a custom block type, it needs to implement interface ITinyMceAdaptiveImage
License file
The license file, called AdaptiveImagesLicense.xml is expected to be found in the website root. You may specify a different path to the license file by setting an app setting like:
<add key="AdaptiveImages:LicenseFile" value="~/App_Data/AdaptiveImagesLicense.xml" />
Web app configuration
You should set an appropriate maximum content length to allow large enough requests if you're using one or more image providers supporting uploads:
<location path="AdaptiveImages">
<!-- Max upload file size ~10 MB-->
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="10485760" /><!-- Note: bytes-->
</requestFiltering>
</security>
</system.webServer>
<system.web>
<httpRuntime maxRequestLength="10240" /><!-- Note: KB -->
</system.web>
</location>
Image providers
The add-on supports multiple image providers.
There are ready-made image providers for some digital asset management (DAM) systems. Please contact us if you want to integrate an existing DAM or looking for a suitable one.
Add/remove image providers
Image providers are types implenting the IImageProvider interface:
var customProvider = new MyCustomProvider();
ImageProviderFactory.Instance.Register(customProvider);
To remove an image provider:
ImageProviderFactory.Instance.Remove(customProvider.Name);
The user interface lists image providers in the order they appear in ImageProviderFactory:
Dropdowns are displayed for the selected image provider's options. For hierarchical options, multiple dropdowns are displayed.
Note: Only image providers with at least one searchable option (ImageProviderOptionCapability.Search capability) are displayed.
Note that commercial use of image providers requires a "Standard" add-on license or above. Visit episerverimages.com for more information.
Security
The add-on uses fixed group names for authorization of REST store endpoints etc. Regular users must belong to either the standard CmsEditors virtual role, or a role called AdaptiveImagesEditors. Administrator plugins require users to belong to either the standard CmsAdmins role, or a role called AdaptiveImagesAdmins.
Development environment
When using a CDN, it requires access to the website to retrieve images for transformation, so your development environment needs to be accessible over the internet.
In other words, if the website is run locally on localhost, you need to make it accessible over the internet when CDN features are enabled.
For local development, we recommend disabling CDN features by removing or clearing the the Cloudinary:CloudName app settings. However, this will affect auto-cropping features. Note that production or high-load environments should always have a CDN enabled.
ngrok is one viable option for enabling a proxy to make your local development environment accessible over the internet.
Troubleshooting
To troubleshoot errors in the Episerver UI, please enable UI debugging in web.config:
<episerver.framework>
<clientResources debug="true" />
</episerver.framework>
Note: Ensure this setting is removed/disabled for production environments.