Adding a Push CDN to Sitecore 6.6

October 8, 2014

There is a setting in Sitecore's configuration that should make adding a push cdn simple.  The Media.MediaLinkPrefix can be used to add a different cdn domain.  Setting the value to "/cdn.domain.com/~/media/" worked for most of our images.  However, any images added into the rich text editor would duplicate the prefix when rendered to a page so we were getting urls like the following:

cdn.domain.com///cdn.domain.com/~/media/...

Not only does this create ugly urls, but it doesn't even use the cdn.

To fix this I took a different approach involving changing the url via the MediaProvider.  There are several good articles on configuring pull cdns that you can find in the references.  This solution borrows from the All Things Sitecore article.

Configuration Changes

First, make sure that the Media.MediaLinkPrefix setting is the default empty string.  If it is different, you'll want to change your prefix values below accordingly.

Next, we have to create a new config file in the /App_Config/Include/ directory.  I named mine, cdn.config, and added the following contents:

<?xml version="1.0" encoding="utf-8" ?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
	<sitecore>
		<hooks>
			<hook type="CustomLibrary.MediaProvider, CustomLibrary">
			<prefix>//cdn.domain.com/</prefix>
		</hook>
	</hooks>
	<mediaLibrary>
		<mediaPrefixes>
			<prefix value="//cdn.domain.com/~/media"/>
		</mediaPrefixes>
	</mediaLibrary>
	</sitecore>
</configuration>

The mediaPrefix, //cdn.domain.com/~/media, is necessary to prevent the prefix duplication.  Requests will still get routed through the cdn and Sitecore treats it as a valid prefix.

Code Changes

Notice the CustomLibrary.MediaProvider, CustomLibrary type in the configuration above.  CustomLibrary needs to be a dll in your project.  CustomLibrary.MediaProvider needs to be a class in that dll that contains the following code:

using Sitecore.Data.Items;
using Sitecore.Events.Hooks;
using Sitecore.Resources.Media;
namespace CustomLibrary
{
	public class MediaProvider : Sitecore.Resources.Media.MediaProvider, IHook
	{
		public void Initialize()
		{
			MediaManager.Provider = this;
		}
		public override string GetMediaUrl(MediaItem item)
		{
			string mediaUrl = base.GetMediaUrl(item);
			return GetMediaUrl(mediaUrl, item);
		}
		public override string GetMediaUrl(MediaItem item, MediaUrlOptions options)
		{
			string mediaUrl = base.GetMediaUrl(item, options);
			return GetMediaUrl(mediaUrl, item);
		}
		/// <summary>
		/// Adds CDN domain
		/// </summary>
		/// <param name="mediaUrl"></param>
		/// <param name="item"></param>
		/// <returns></returns>
		public string GetMediaUrl(string mediaUrl, MediaItem item)
		{
			//verify the domain was set in the config
			if (string.IsNullOrEmpty(Prefix))
				return mediaUrl;
			else if (Sitecore.Context.Database != null && Sitecore.Context.Database.Name != "web")
			{
				//Make sure cdn isn't used for master database
				if (mediaUrl.Contains(Prefix))
					return mediaUrl.Replace(Prefix, "/");
				else
					return mediaUrl;
			}
			else if (mediaUrl.Contains(Prefix))
				return mediaUrl;
			else
			{
				mediaUrl = RemoveDomain(mediaUrl);
				//clean the url
				if (mediaUrl.StartsWith("/"))
				mediaUrl = mediaUrl.Substring(1);
				//this happens while indexing unless the proper site is set
				mediaUrl = mediaUrl.Replace("/sitecore/shell/", "");
				mediaUrl = string.Format("{0}{1}", Prefix, mediaUrl);
				return mediaUrl;
			}
		}
		public string RemoveDomain(string url)
		{
			if (url.Contains("https://"))
				return url.Replace("https://" + Sitecore.Context.Site.HostName, "");
			else if (url.StartsWith("//"))
				return url.Substring(2);
			else
				return url.Replace("http://" + Sitecore.Context.Site.HostName, "");
		}
		/// <summary>
		/// Property defined in the config
		/// </summary>
		public string Prefix { get; set; }
	}
}

Summary

By using a custom MediaProvider hook, we can ensure that images added through a rich text editor in Sitecore 6.6 rev 140410 use the correct url when using a cdn.  Also, by keeping the code and configuration separate we make it easy to remove if Sitecore fixes this in a future release.

How to Make it Better

This code uses one push cdn domain on all images in a Sitecore solution because that is all that we needed.  However, you could extend this code to use multiple cdn domains or only apply to certain Sitecore sites.  See the references below for additional options.

References

If you are looking for a pull cdn solution or more background on integrating a cdn into Sitecore, check out the following: