In a recent project, we had the need to serve website assets and website content images through a CDN network. Furthermore, each image presented in the website content must be available in multiple resolutions.

We ended up serving both the assets and content images through the same Microsoft Azure CDN. This post describes the way we implemented it.

First the easiest part. We have our site in a Azure Website, in order to serve the site’s assets through an Azure CDN we only needed to point the CDN to the website’s URL. So, if the website’s assets are located in something like http://sitedomain/assets/*, instead of referencing the assets through a relative url such as /assets/*, we just need to use the CDN url to serve them: http://cdnurl/assets/*. Through web.config transforms we are also able to use local assets during development and change to CDN only on the production environment.

Regarding the website image content, the solution has three phases.

1. The content is managed over SharePoint 2013, and the images are no exception. We created a feature receiver that uploads to Azure Blob Storage every time an image is uploaded, or modified,  on any Picture Library, and then save its known CDN URL in the library item.

string cdnUrl = Configuration.Settings.ImageUploader.CdnUrl;
string containerStr = Configuration.Settings.ImageUploader.Container;

// Retrieve storage account from connection string.
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(

// Create the blob client.
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();

// Retrieve reference to a container.
CloudBlobContainer container = blobClient.GetContainerReference(containerStr);

// Creates the container if it doesn't exist

// Set the blobs access level to public
container.SetPermissions(new BlobContainerPermissions
    PublicAccess = BlobContainerPublicAccessType.Blob

string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file.Name);
string extension = Path.GetExtension(file.Name);

fileNameWithoutExtension += "-" + DateTime.Now.Ticks;

// Build file name to upload
string fileName = string.Format("{0}/{1}{2}", file.Item.ParentList.Title.ToLower(), fileNameWithoutExtension.Replace(" ", "-"), extension).ToLower();

// Retrieve reference to a blob named "".
CloudBlockBlob blockBlob = container.GetBlockBlobReference(fileName);

// Create or overwrite the "" blob with contents from a local file.
using (var fileStream = file.OpenBinaryStream())

    // Update SP item CDN URL column
    string finalUrl = HttpUtility.UrlPathEncode(string.Format("{0}/{1}/{2}?{3}", cdnUrl, containerStr, fileName, IMAGE_RESIZER_FIRST_PARAMETER));

    properties.ListItem[Configuration.Settings.SP_COLUMN_CDN_URL] = finalUrl.ToLower();

2. Having the images in a Azure Blob Storage, we decided to use the Image Resizer ASP.NET Library and its AzureReader2 plugin to resize and serve images directly through our Azure Website. The implementation is as easy as installing a nuget package: Image Resizer. After configuring the Blob Storage endpoint, and setting the prefix (we use ~/azure), we are able to access the images through an URL such as: http://sitedomain/azure/image/path/in/blobstorage. With this working we can resize and modify the images in any way available to us with the Image Resizer library and described in detail here:

3. Since our CDN is pointing directly to our Azure Website, we can use the CDN URL instead of the azure website one. For instance, the fake image I talked about the previous bullet can be accessed with the following URL: http://cdnurl/azure/image/path/in/blobstorage. This way, the first time someone opens an image through the CDN URL it is handled by the Image Resizer library. The  second time, CDN has already cached it and distributed it around the world and it is served much faster!


  1. Hi André

    Great article…. Is there something special required here to make this work. I’m running with the AzureReader2, disabled my DiskCache, and added a CDN:
    Origin hostname:
    Origin host header:
    Origin path: /azure

    But, this original path works.
    But the new CDN path doesn’t

    What am I missing here ?

    Regards, Thomas


Please enter your comment!
Please enter your name here