Setting up Azure CDN to deliver your Sitecore Media

At SUGCON EU 2016 I presented about the different options of using Content Delivery Networks with Sitecore. At the time, I had been working on a particular task to offload large media items into Azure Blob storage and serve them to via Azure CDN and wrote a number of posts detailing how I achieved this.

One of the options that I presented was utilising Azure CDN to serve your media, allowing you to benefit from Azure’s Geo-located Edge Servers meaning that assets are served from locations closer to your users, your own servers can focus on just delivering content (possibly meaning less content delivery servers and licensing costs) as well as improving browser response times by domain sharding the requests.

Use of Azure CDN will work with any version of Sitecore, and is not specific to Sitecore 8.2 Update-1 which added Azure Web Apps support. In fact, you don’t even need to be hosting your servers in Azure to utilise the CDN service.

I’ve been asked by several Sitecorians about configuring CDN, so I thought I would share a step-by-step guide in setting up Azure CDN with Sitecore.

there-is-no-cloud

Sitecore with Azure CDN Walkthrough

Add a CDN profile to your Azure account:

cdn-01-create-profile

And configure the CDN profile by giving it a name and selecting a pricing tier:

cdn-02-configure-profile

It’s worth noting that although a Resource Group Location is required, CDN by it’s very nature is not tied to a specific datacentre:

The Azure CDN service is global and not bound to a location. However, you must specify a location for the resource group where the metadata associated with the CDN profile will reside. This location will have no impact on the runtime availability of your profile.

Wait for the CDN profile to be created, it should take a couple of minutes. Once that is done, you’re ready to set up some endpoints to tell the CDN where your site is and where to fetch data from:

cdn-03-configure-endpoint

Add a unique name for your Endpoint, this will form the URL for your CDN in the format endpoint-name.azureedge.net.

Ensure you set Origin type as Custom Origin.

Then add in the details of your live Sitecore site. This should be the publicly accessible site, e.g. the CD server or the load balancer. The site doesn’t actually need to be hosted on Azure.

Once the endpoint is created you’ll be able to find the Endpoint hostname listed, we will require this later for configuration in Sitecore.

cdn-04-endpoint-address

Note, it can take up to 90 minutes to for the endpoint to be created and propagate to the Edge Servers.

Finally, set the Cache to Cache every unique URL:

cdn-05-cache-settings

This will ensure that query parameters are taken into account. This is good, esp for resized images where height and width parameters are supplied (as well as media hash). We’ll also make use of this feature a little later in our Sitecore set up.

Don’t like that Azure domain name? There’s an option to add your own custom domain, so you could map a sub-domain like media.yoursite.com to point to this Azure CDN Endpoint instead.

That’s all that needed in Azure. Simple eh.

Sitecore Changes to Support CDN

This follows some advice from my previous article about domain sharding in Sitecore, but now we have Custom Origin support in Azure…

<setting name="Media.MediaLinkServerUrl">
  <patch:attribute name="value">//jammycdn.azureedge.net</patch:attribute>
</setting>

Set the Media Server URL to the Azure endpoint from earlier. We’re going to use a protocol-less URL here, so if the site runs in HTTP it will use that, if it’s currently under HTTPS it will use that instead. You can just use http: or https: if you want though.

<setting name="Media.AlwaysIncludeServerUrl">
  <patch:attribute name="value">true</patch:attribute>
</setting>

This will cause the Media Link Provider to include the above URL in the media links that are rendered. You only need this on the CD servers, leaving this as false on CM will mean those assets on that server are rendered from the database as normal.

<!--  MEDIA RESPONSE - CACHEABILITY
        The <see cref="HttpCacheability">cacheability</see> to use in media response headers.
        Possible values: NoCache, Private, Public, Server, ServerAndNoCache, ServerAndPrivate
        Default value: private
  -->
<setting name="MediaResponse.Cacheability">
  <patch:attribute name="value">public</patch:attribute>
</setting>

We need to set the cache-control headers to public, otherwise the CDN will not cache our media, making the exercise rather pointless.

In case you’re wondering about those cache settings, most articles only seem to talk about no-cache, private and public. The setting is actually a wrapper for
HttpCacheability Enumeration setting in System.Web namespace. You can read more about this setting in the official Microsoft documentation.

How are requests served?

So how does this all work? When Sitecore renders the link to the media on your CD with these settings it will look like this:

<img src="//jammykam.azureedge.net/-/media/path-to/image.png?la=en&h=123&w=123&hash=1a2b3c4d5e6f7g8h9i0" height="123px" width="123px" />
  1. Your browser requests the image from the CDN Endpoint
  2. The endpoint does not have this image in cache
    • CDN calls your website (that you specified in Azure as the origin host)
  3. Website returns the image, CDN adds it to cache
  4. CDN returns image to user, everyone is happy
  5. Subsequent requests for the same asset…
  6. If the image has previously been called with these parameters then the CDN has it in cache and returns it to the user.

The typical journey looks something like this:

cdn-06-overview

  • So the first request for an image on a fresh cache always makes a request to your CD server. Any subsequent ones do not.
  • CDNs can be distributed anywhere globally
  • The HTML of your site is always served from your actual CD server, although there is no reason why the HTML could not be cached if the correct cache header is sent

Invalidating Caches

The problem now is caching on the CDN itself.

If you change an image for another, the image size/parameters may stay the same (on a scaled image for example) and the path may not change. So you need to somehow tell the CDN to remove its cache.

Azure CDN TTL / Cache expiry is 7 days by default. I couldn’t find a way of setting this in the portal, I think it needs it set on upload (into blob storage) but we are not using that, or possibly the cache header of the original image from Sitecore.

You can do this by manually purging:

cdn-07-purge-cache

Or you can hook up a publish:end processor and call the REST API to clear the CDN cache. You need to write this but pretty simple REST call with webclient.

This is pretty aggressive though because it means EVERYTHING is purged, not just the media that has changed. It may be fine for your requirements, it just means that everything will get automatically re-fetched again as required.

An alternative is to make use of the “Cache every unique URL” feature we enabled earlier, and update the Media Link Provider to also append either the Updated Date or the Revision Field of the media item.

This will cause the URLs to look like:

//jammycdn.azureedge.net/-/media/path-to/image.png?la=en&h=123&w=123&hash=1a2b3c4d5e6f7g8h9i0&modified=20160126120123
//jammycdn.azureedge.net/-/media/path-to/image.png?la=en&h=123&w=123&hash=1a2b3c4d5e6f7g8h9i0&revision={guid}

Either way will do, since these values change when the image is updated or a new version added. All good.

Updating Media Provider

Create a new class, inheriting from the default MediaProvider and append the revision or the modified date:

using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Resources.Media;

namespace MyProject.CMS.Custom.Media
{
    public class MediaProvider: Sitecore.Resources.Media.MediaProvider
    {
        public override string GetMediaUrl(MediaItem item)
        {
            Assert.ArgumentNotNull((object)item, "item");
            return this.GetMediaUrl(item, MediaUrlOptions.Empty);
        }
 
        public override string GetMediaUrl(MediaItem item, MediaUrlOptions options)
        {
            Assert.ArgumentNotNull((object) item, "item");
            Assert.ArgumentNotNull((object) options, "options");
 
            string mediaURL = base.GetMediaUrl(item, options);
 
            mediaURL = Sitecore.Web.WebUtil.AddQueryString(mediaURL, new string[] {"revision", ((Item)item).Statistics.Revision });
            //OR
            mediaURL = Sitecore.Web.WebUtil.AddQueryString(mediaURL, new string[] {"modified", ((Item)item).Statistics.Updated.ToString("yyyyMMddHHmmss") });
 
            return mediaURL;
        }
    }
}

And update the config to point to your new class.

<mediaLibrary>
  <mediaProvider>
    <patch:attribute name="type">MyProject.CMS.Custom.Media.MediaProvider, MyProject.CMS.Custom</patch:attribute>
  </mediaProvider>
</mediaLibrary>

As you can see, it’s very simple to set up. Apologies if my previous blog post caused confusion and made it seem difficult, but that was solving a very specific problem – off-loading very large files into Azure Blob Storage (currently over 60GB and counting!).

As always, feel free to reach out if you have any questions.

SUGCON Presentation

You can watch my presentation from SUGCON 2016 here:

There are several other presentations on that playlist which I highly recommend watching.

You can also download the slides from my presentation.

Related Links

Advertisements

5 comments

  1. FAIYAZ (@faiyazulnoor) · February 14

    You are amazing JammyKam ! Insightful explanation

  2. Dan Cruickshank · February 16

    This post is prettay prettay good. *finger guns*

  3. Stijn Planckaert · February 17

    Nice post.

    Using the ‘Purge’ options doesn’t seem like a usable option to me because, in addition to the things you already mentioned, the browser will still serve the old image from its cache until cache expiration.

    We’re using azure cdn for a while in combination with sitecore, and always use the patched media provider.

  4. Jarmo Jarvi · May 2

    This is excellent! Trying to decide between Akamai and Azure CDN.

  5. Pingback: Sitecore Media Library integration with Azure CDN using origin pull | Brian Pedersen's Sitecore and .NET Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s