Why Clydesdale?
Expertise
Blog
Contact
Home
Clydesdale Software Logo


Follow Us: Twitter RSS



Go Back

Umbraco in Azure

I’m sure I’m not the only one that started looking for a better way to have Umbraco in Azure then using the Umbraco Accelerator that hits blob storage every second.  There are many limitations to the accelerator and after spending a day modifying it I decided to go another direction.


Firstly understand my usage scenario may not be the same as others.  I use a CDN for public content (such as images) and I created a handler to retrieve private content (such as user downloads) from blob storage.  For css, master page changes, etc I deploy then vip swap.  Quite honestly this is not what I use Umbraco for.  I do not upload images or private content to Umbraco so I do not need file syncing at the moment, though I do have some ideas on how to achieve this with notifications and web services.


The main issue is Umbraco Caching.  When content is published it needs to notify all the instances in Azure so the cache can be updated.


Umbraco supports load balanced environments so the trick is setting it up in Azure.


1) Add <Runtime executionContext="elevated" /> to your service definition right under the WebRole element.


2) Make sure umbracoUseSSL in the web.config appSettings is set to false.  The reason for this is each server listed in the umbracoSettings.config has a webservice called made on it.  The internal Umbraco code uses this setting and will try to use https for this call and since these are internal ips there will be a cert error and it will not work.  You can still access the admin portal with https, it will just not auto redirect (could probably use url rewriting to get around this).


3) In umbracoSettings.config enable distributedCall.


4) Use the UmbracoAzureSetup class to get load balancing setup. Add UmbracoAzureSetup.Setup() to your OnStart of the WebRole.

NOTE 1: Please read through the UmbracoAzureSetup class because it is coded with some limitations that may require a code change to meet your requirements.

NOTE 2: I recommend wrapping your code in OnStart in a try/catch and write to the event log on error so you can get details when something goes wrong.

5) Enjoy Umbraco in Azure and watch caching work like a charm.


Details of the UmbracoAzureSetup Class:

This class is pretty simple, it does two basic things.  One, sets full control permissions for NETWORK SERVICE and two, updates the umbracoSettingsConfig with the ip addresses of the instances in Azure.


// ----------------------------------------------------------------------------------
//  Unified Logging
// 
//  Copyright (c) UL Analytics, LLC. All rights reserved.
// 
//  ----------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.IO;
using System.Security.AccessControl;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using Microsoft.WindowsAzure.ServiceRuntime;

namespace UnifiedLogging.Web.Site
{
    public static class UmbracoAzureSetup
    {
        #region public Static Methods

        /// <summary>
        /// Sets up umbraco for load balancing in Azure.
        /// Things to Note:
        /// 1) This is coded to work with one Site element under Sites in the RoleModel.xml.
        /// Update the code if this is not your case.
        /// 2) Sets permissions for NETWORK SERVICE, if the app pool is different update the code.
        /// </summary>
        public static void Setup()
        {
            SetPermissions();

            UpdateUmbracoSettings();
        }

        #endregion

        #region private Static Methods

        /// <summary>
        /// This will get the attribute of the first Site under Sites in the RoleModel.xml. 
        /// If there are multpile site elements be sure to update this method to return the correct physical path.
        /// </summary>
        /// <returns></returns>
        private static string GetWebSiteDirectory()
        {
            var rootPath = string.Empty;

            var roleRootDir = Environment.GetEnvironmentVariable("RdRoleRoot");

            var roleModelDoc = XDocument.Load(Path.Combine(roleRootDir, "RoleModel.xml"));
            var namespaceManager = new XmlNamespaceManager(new NameTable());
            namespaceManager.AddNamespace("empty",
                                          "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition");

            var siteElement = roleModelDoc.XPathSelectElement("/empty:RoleModel/empty:Sites/empty:Site",
                                                              namespaceManager);

            if (siteElement != null)
            {
                var attr = siteElement.Attribute("physicalDirectory");
                if (attr != null)
                {
                    rootPath = Path.Combine(roleRootDir, attr.Value);
                }
            }

            return rootPath + Path.DirectorySeparatorChar;
        }

        /// <summary>
        /// Will set full control permissions for NETWORK SERVICE on the entire web directory.
        /// </summary>
        private static void SetPermissions()
        {
            var roleRootDir = GetWebSiteDirectory();

            if (String.IsNullOrEmpty(roleRootDir) == false)
            {
                roleRootDir = roleRootDir.TrimEnd('\\');

                var accessControl = Directory.GetAccessControl(roleRootDir);
                accessControl.AddAccessRule(new FileSystemAccessRule("NETWORK SERVICE", FileSystemRights.FullControl,
                                                                     InheritanceFlags.ContainerInherit |
                                                                     InheritanceFlags.ObjectInherit,
                                                                     PropagationFlags.None,
                                                                     AccessControlType.Allow));

                Directory.SetAccessControl(roleRootDir, accessControl);
            }
        }

        /// <summary>
        /// Updates the umbracosetting.config with the ip addresses of the instances in the role.
        /// </summary>
        private static void UpdateUmbracoSettings()
        {
            var serverIps = new List<string>();

            if (RoleEnvironment.IsAvailable)
            {
                foreach (RoleInstance roleInstance in RoleEnvironment.CurrentRoleInstance.Role.Instances)
                {
                    foreach (RoleInstanceEndpoint endpoint in roleInstance.InstanceEndpoints.Values)
                    {
                        var ipString = endpoint.IPEndpoint.Address.ToString();

                        if (serverIps.Contains(ipString) == false)
                        {
                            serverIps.Add(ipString);
                        }
                    }
                }
            }

            WriteDistributedServers(serverIps);
        }

        private static void WriteDistributedServers(List<string> serverIps)
        {
            var websiteRoot = GetWebSiteDirectory();
            //taken from umbraco source:)
            var configPath = websiteRoot + "config" +
                             Path.DirectorySeparatorChar + "umbracoSettings.config";

            var umbracoSettings = XDocument.Load(configPath);
            var serversElement = umbracoSettings.XPathSelectElement("/settings/distributedCall/servers");

            if (serversElement != null)
            {
                serversElement.RemoveNodes();

                foreach (var serverIp in serverIps)
                {
                    var serverElement = new XElement("server", serverIp);
                    serversElement.Add(serverElement);
                }

                umbracoSettings.Save(configPath);
            }
        }

        #endregion
    }
}

Facebook Twitter DZone It! Digg It! StumbleUpon Technorati Del.icio.us NewsVine Reddit Blinklist Add diigo bookmark