Microsoft provide a CDN service on the cloud. This service needs to take the static files from a local folder on the same website. I'm going to show you how to add to Sitecore the feature to clone all the files of the Media Library into this folder. This action is going to happen just when you publish those items from the Master database to Web database. Also, you will have to accomplish some extra configurations on the Content Delivery servers to take them from the CDN servers.
I'm going to show you and I'm going to provide you a package to use Azure CDN with your Sitecore 7.* project hosted in Azure.
This package is already on the Sitecore Marketplace. Also, the source code is on Github too.
Step 1 - Create the CDN service on Azure.
Got to the App Services section. Press on the CDN option. Then press on the Quick Create button. On the Origin Domain dropdown select the URL of your website. (Note: CDN is going to search the images on a CDN folder in the root of your website). After the creation of the CDN service, I'll recommend to enable HTTPS on it.
Step 2 - Install the package and config it
You have to install the package on the site that you have selected on the dropdown when you create the Azure CDN service.
After the installation, you will see a new file in the folder /App_Config/Includes. The file is called SitecoreFromArg.CdnAzure.config and it has 2 parameters inside:
Enabled: This parameter allow you to disable this feature.
<Enabled>yes</Enabled>
SourceDomain: You can use this parameter to change the name of the default folder where Azure is looking for the static files.
<SourceDomain>/cdn</SourceDomain>
Step 3 - Republish media items
Go to Content Editor on the server where you have installed the package, and republish all the items of the Media Library folder.
Final step - Change the configurations on the Content Delivery servers
Set the parameter Media.AlwaysIncludeServerUrl in true:
<setting name="Media.AlwaysIncludeServerUrl" value="true" />
Add the following string in the parameter Media.MediaLinkPrefix:
<setting name="Media.MediaLinkPrefix" value="~/"/>
Add the CDN Domain on the parameter Media.MediaLinkServerUrl (this parameter isn't on Sitecore 6.*):
<setting name="Media.MediaLinkServerUrl" value="http://aabbccdd.vo.msecnd.net"/>
Finally, remove the attribute value="ashx" from the parameter Media.RequestExtension:
<setting name="Media.RequestExtension" />
There is the code of my package
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using Sitecore.Data.Items; | |
using Sitecore.Diagnostics; | |
using Sitecore.Jobs; | |
using Sitecore.Publishing.Pipelines.PublishItem; | |
namespace SitecoreFromArg.CdnAzure | |
{ | |
public class CdnPublishing : PublishItemProcessor | |
{ | |
public string Enabled { get; set; } | |
public string SourceDomain { get; set; } | |
public override void Process(PublishItemContext context) | |
{ | |
if (Enabled.ToLower() != "yes") return; | |
Log.Debug("Performing CDN validations", this); | |
Assert.ArgumentNotNull(context, "context"); | |
var contextItem = context.PublishHelper.GetSourceItem(context.ItemId); | |
if (contextItem == null) return; | |
if (!contextItem.Paths.IsMediaItem) return; | |
MediaItem mediaItem = contextItem; | |
if (string.IsNullOrEmpty(mediaItem.Extension)) return; | |
var mediaStream = mediaItem.GetMediaStream(); | |
if (mediaStream == null || mediaStream.Length == 0) return; | |
var processor = new CdnAzureProcessor(SourceDomain); | |
Log.Debug("Starting CDN synchonization", this); | |
try | |
{ | |
Sitecore.Context.Job.Status.State = JobState.Initializing; | |
if (context.Action == Sitecore.Publishing.PublishAction.None) | |
processor.UploadFileOnCdn(mediaItem); | |
if ((context.Action == Sitecore.Publishing.PublishAction.PublishSharedFields) || | |
(context.Action == Sitecore.Publishing.PublishAction.PublishVersion)) | |
processor.ReplaceFileOnCdn(mediaItem); | |
if (context.Action == Sitecore.Publishing.PublishAction.DeleteTargetItem) | |
processor.DeleteFileOnCdn(mediaItem); | |
Sitecore.Context.Job.Status.State = JobState.Finished; | |
} | |
catch (Exception ex) | |
{ | |
var cdnEx = new Exception(string.Format("CDN Processing failed for {1} ({0}). {2}", contextItem.ID, contextItem.Name, ex.Message)); | |
Log.Error(cdnEx.Message, cdnEx, this); | |
context.Job.Status.Failed = true; | |
context.Job.Status.Messages.Add(cdnEx.Message); | |
} | |
Log.Debug("CDN synchronization finished", this); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/"> | |
<sitecore> | |
<pipelines> | |
<publishItem> | |
<processor type="SitecoreFromArg.CdnAzure.CdnPublishing,SitecoreFromArg.CdnAzure" | |
patch:before="processor[@type='Sitecore.Publishing.Pipelines.PublishItem.RemoveUnknownChildren, Sitecore.Kernel']"> | |
<Enabled>yes</Enabled> | |
<SourceDomain>/cdn</SourceDomain> | |
</processor> | |
</publishItem> | |
</pipelines> | |
</sitecore> | |
</configuration> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.IO; | |
using System.Text; | |
using Sitecore; | |
using Sitecore.Data.Items; | |
using Sitecore.Diagnostics; | |
using System.Web; | |
namespace SitecoreFromArg.CdnAzure | |
{ | |
public class CdnAzureProcessor | |
{ | |
public string CdnSourceDomain { get; set; } | |
public CdnAzureProcessor(string cdnSourceDomain) | |
{ | |
var cdnFolderPath = GetCdnFolderPath(cdnSourceDomain); | |
if (!Directory.Exists(cdnFolderPath)) Directory.CreateDirectory(cdnFolderPath); | |
CdnSourceDomain = cdnSourceDomain; | |
} | |
public void UploadFileOnCdn(MediaItem mediaItem) | |
{ | |
var filePath = GetFilePath(mediaItem); | |
var mediaStream = mediaItem.GetMediaStream(); | |
var exist = File.Exists(filePath); | |
if (exist) | |
{ | |
ReplaceFileOnCdn(mediaItem); | |
} | |
else | |
{ | |
var directoryPath = Path.GetDirectoryName(filePath); | |
if (directoryPath == null) return; | |
Directory.CreateDirectory(directoryPath); | |
using (var fileStream = File.Create(filePath)) | |
{ | |
mediaStream.CopyTo(fileStream); | |
} | |
Log.Debug(string.Format(@"CDN has added: {0}", filePath), this); | |
} | |
} | |
public void ReplaceFileOnCdn(MediaItem mediaItem) | |
{ | |
var filePath = GetFilePath(mediaItem); | |
var exist = File.Exists(filePath); | |
if (exist) | |
{ | |
DeleteFileOnCdn(mediaItem); | |
UploadFileOnCdn(mediaItem); | |
} | |
else | |
{ | |
UploadFileOnCdn(mediaItem); | |
} | |
} | |
public void DeleteFileOnCdn(MediaItem mediaItem) | |
{ | |
var filePath = GetFilePath(mediaItem); | |
var exist = File.Exists(filePath); | |
if (exist) | |
{ | |
File.Delete(filePath); | |
} | |
Log.Debug(string.Format(@"CDN has deleted: {0}", filePath), this); | |
} | |
[NotNull] | |
private string GetFilePath(MediaItem mediaItem) | |
{ | |
var builder = new StringBuilder(); | |
builder.Append(HttpRuntime.AppDomainAppPath.Remove(HttpRuntime.AppDomainAppPath.Length - 1)); | |
builder.Append(CdnSourceDomain.Replace('/', '\\')); | |
builder.Append(mediaItem.MediaPath.Replace('/', '\\')); | |
builder.Append("."); | |
builder.Append(mediaItem.Extension); | |
return builder.ToString(); | |
} | |
[NotNull] | |
private string GetCdnFolderPath(string cdnSourceDomain) | |
{ | |
var builder = new StringBuilder(); | |
builder.Append(HttpRuntime.AppDomainAppPath.Remove(HttpRuntime.AppDomainAppPath.Length - 1)); | |
builder.Append(cdnSourceDomain.Replace('/', '\\')); | |
return builder.ToString(); | |
} | |
} | |
} |