Monday, September 21, 2009

Copy web.config settings using a SharePoint Feature

Scenario:
Copy web.config settings using a SharePoint Feature

Explanation:
For most of the projects, it is usually required to add few web.config entries.
Adding them in the WFE servers manually is prone to mistakes.

Deploying them as a feature ensures that its not only clean but also eases the job of administrators if a new WFE server is added in the future.

Lets take the scenario where we had to update web.config with entries for Telerik Controls

Code:
//Feature.xml
<?xml version="1.0" encoding="utf-8"?>
<Feature
  Id="[ID]"
  Title="Telerik Controls"
  Description="This feature adds the required Telerik Web.Config entries and assosiated dlls"
  Version="1.0.0.0"
  Scope="WebApplication"
  Hidden="false"
  ImageUrl="TelerikControls\img.jpg"
  ReceiverAssembly="TelerikControls, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d6ff03c5a94c295d"
  ReceiverClass="TelerikControls.TelerikControlsFeatureReceiver" 
  xmlns="http://schemas.microsoft.com/sharepoint/">
  <ElementManifests>
    <ElementFile Location="Web.Config" />
  </ElementManifests>
</Feature>


//Web.Config
<configuration>
  <!-- SafeControls for Telerik -->
  <SharePoint>
    <SafeControls>
      <SafeControl Assembly="Telerik.Web.Design, Version=2009.2.701.35, Culture=neutral, PublicKeyToken=121fae78165ba3d4" Namespace="Telerik.Web.Design" TypeName="*" Safe="True" />
      <SafeControl Assembly="Telerik.Web.UI, Version=2009.2.701.35, Culture=neutral, PublicKeyToken=121fae78165ba3d4" Namespace="Telerik.Web.UI" TypeName="*" Safe="True" />
    </SafeControls>
  </SharePoint>
  <system.web>
    <httpHandlers>
      <add verb="*" validate="false" path="Telerik.Web.UI.DialogHandler.axd" type="Telerik.Web.UI.DialogHandler, Telerik.Web.UI, Version=2009.2.701.35, Culture=neutral, PublicKeyToken=121fae78165ba3d4" />
    </httpHandlers>
  </system.web>
</configuration>


//FeatureReceiver
using System.Xml;

using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;

namespace TelerikControls
{
    public class TelerikControlsFeatureReceiver : SPFeatureReceiver
    {

        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            string xPath;
            SPWebApplication webApp;
            WebConfigModifications webConfigMods;
            XmlDataDocument xmlDoc;

            webApp = (SPWebApplication)properties.Feature.Parent;
            webConfigMods = new WebConfigModifications();

            //load the web.config settings from the feature root
            xmlDoc = new XmlDataDocument();
            xmlDoc.Load(string.Format("{0}\\{1}", properties.Feature.Definition.RootDirectory, "web.config"));

            //configuration/SharePoint/SafeControls
            xPath = "configuration/SharePoint/SafeControls";
            AddWebConfigNodes(xPath, ref xmlDoc, ref webConfigMods, ref webApp);

            //configuration/system.web/httpHandlers
            xPath = "configuration/system.web/httpHandlers";
            AddWebConfigNodes(xPath, ref xmlDoc, ref webConfigMods, ref webApp);

            //set full trust
            SPWebConfigModification modification = new SPWebConfigModification("level", "configuration/system.web/trust");
            modification.Owner = "WebConfigTrust";  
            modification.Sequence = 0;
            modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureAttribute;
            modification.Value = "Full";
            webApp.WebConfigModifications.Add(modification);

            webApp.Farm.Servers.GetValue<SPWebService>().ApplyWebConfigModifications();
            webApp.Update();

        }

        
        private void AddWebConfigNodes(string xPath, ref XmlDataDocument xmlDoc, ref WebConfigModifications webConfigMods, ref SPWebApplication webApp)
        {
            foreach (XmlNode node in xmlDoc.SelectSingleNode(xPath))
                if (node.NodeType != XmlNodeType.Comment)
                    webConfigMods.AddWebConfigNode(webApp, xPath, node, node.Attributes);
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            SPWebApplication webApp = (SPWebApplication)properties.Feature.Parent;
            WebConfigModifications webConfigMods = new WebConfigModifications();
            webConfigMods.RemoveWebConfigNodes(webApp);
            webApp.Farm.Servers.GetValue<SPWebService>().ApplyWebConfigModifications();
            webApp.Update();
        }

        public override void FeatureInstalled(SPFeatureReceiverProperties properties)
        {
            /* no op */
        }
        public override void FeatureUninstalling(SPFeatureReceiverProperties properties)
        {
            /* no op */
        }
    }
}


//Helpers
using System.Xml;
using System.Text;
using Microsoft.SharePoint.Administration;
using System.Collections.ObjectModel;

namespace TelerikControls
{
    public class WebConfigModifications
    {

        public string Owner
        {
            get
            {
                return this.GetType().FullName;
            }
        }

        private string GetWebConfigModName(string nodeName, XmlAttributeCollection attributes)
        {
            StringBuilder webConfigModName = new StringBuilder(nodeName);


            foreach (XmlAttribute attribute in attributes)
            {
                webConfigModName.Append(string.Format("[@{0}=\"{1}\"]", attribute.Name, attribute.Value));
            }


            return webConfigModName.ToString();
            //return string.Format("add[@key=\"{0}\"]", key);

        }

        private string GetWebConfigModValue(string nodeName, XmlAttributeCollection attributes)
        {
            XmlDataDocument xDoc = new XmlDataDocument();
            XmlAttribute newAttribute;
            XmlNode modValueNode = xDoc.AppendChild(xDoc.CreateElement(nodeName));
            foreach (XmlAttribute attribute in attributes)
            {
                newAttribute = xDoc.CreateAttribute(attribute.Name);
                newAttribute.Value = attribute.Value;
                modValueNode.Attributes.Append(newAttribute);
            }
            return string.Format("{0}\n", modValueNode.OuterXml);
        }

        private string GetFirstNodeName(ref XmlDataDocument xDoc)
        {
            string xPath = xDoc.FirstChild.Name;

            //we don't want to process the xml declaration node
            if (xPath == "xml")
            {
                if (xDoc.FirstChild.NextSibling != null)
                {
                    xPath = xDoc.FirstChild.NextSibling.Name;
                }
                else
                {
                    xPath = string.Empty;
                }
            }

            return xPath;
        }

 


        /// <summary>
        /// Adds the key/value pair as an appSettings entry in the web application's 
        /// SPWebConfigModification collection
        /// </summary>
        /// <param name="webApp">Current web application context</param>
        /// <param name="key">appSettings node key</param>
        /// <param name="value">appSettings node value</param>
        public void AddWebConfigNode(SPWebApplication webApp, string webConfigModxPath, XmlNode node, XmlAttributeCollection attributes)
        {
            SPWebConfigModification webConfigMod;
            string webConfigModName;
            
            webConfigModName = GetWebConfigModName(node.Name, attributes);
            webConfigMod = new SPWebConfigModification(webConfigModName, webConfigModxPath);
            webConfigMod.Owner = this.Owner;
            webConfigMod.Sequence = 0;
            webConfigMod.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
            webConfigMod.Value = node.OuterXml;

            webApp.WebConfigModifications.Add(webConfigMod);
        }

        /// <summary>
        /// Removes the key from the appSettings the web application's 
        /// SPWebConfigModification collection
        /// </summary>
        /// <param name="webApp">Current web application context</param>
        /// <param name="key">appSettings node key</param>        
        public void RemoveWebConfigNodes(SPWebApplication webApp)
        {
            Collection<SPWebConfigModification> collection;
            SPWebConfigModification[] tempCollection;
            string webConfigModName;
            int iStartCount;
            SPWebConfigModification webConfigMod;

            collection = webApp.WebConfigModifications;
            tempCollection = new SPWebConfigModification[collection.Count];
            collection.CopyTo(tempCollection, 0);            
            iStartCount = collection.Count;

            // Remove any modifications that were originally created by the owner.
            for (int c = iStartCount - 1; c >= 0; c--)
            {
                webConfigMod = collection[c];

                if (webConfigMod.Owner == this.Owner)
                    collection.Remove(webConfigMod);
            }
        }
    }
}

2 comments:

  1. What Telerik has to do with this ?

    ReplyDelete
  2. @ Sandeep K Nahta.. It is quite obvious that I just gave an example of a real time scenario. In this case, it happened to be the config entries that need to be added for Telerik controls

    ReplyDelete