Sitecore 8 Config File Sequence

Posted 02/18/2015 by Akshay Sura

My original idea was to extend the ShowConfig.aspx to display other parts of the web.config. For instance to look at the handlers or webservice references in <system.web> and <system.webserver>.

What I found is that for each custom config section we need to specify a IConfigurationHandler class which handles the configuration for that section. Now if you try to cast that section to a different IConfigurationHandler than what is in the config, it will generate an error.

  <configSections>
    <section name="sitecore" type="Sitecore.Configuration.ConfigReader, Sitecore.Kernel"/>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, Sitecore.Logging"/>
    <sectionGroup name="fiftyOne">
      <section name="log" type="FiftyOne.Foundation.Mobile.Configuration.LogSection, FiftyOne.Foundation" requirePermission="false" allowDefinition="Everywhere" restartOnExternalChanges="false" allowExeDefinition="MachineToApplication"/>
      <section name="wurfl" type="FiftyOne.Foundation.Mobile.Detection.Wurfl.Configuration.WurflSection, FiftyOne.Foundation" requirePermission="false" allowDefinition="Everywhere" restartOnExternalChanges="false" allowExeDefinition="MachineToApplication"/>
    </sectionGroup>
    <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
    </sectionGroup>
  </configSections>

That along with other concerns, I abandoned that idea. During the same time I also wanted to build a config file sequence page which will prove useful during debugging. Here is what I came up with:

1. A ConfigSequence.aspx file in /sitecore/admin directory

<%@ Page Language="C#" AutoEventWireup="true" Inherits="YOURNAMESPACE.ConfigSequence" %>

2. A Class to go through the configs and build the sequence

using Sitecore;
using Sitecore.Collections;
using Sitecore.Diagnostics;
using Sitecore.Xml;
using System;
using System.Collections;
using System.IO;
using System.Xml;

namespace YOURNAMESPACE
{
    /// <summary>
    /// The Factory.
    /// </summary>
    public class FactoryEx
    {
        /// <summary>
        /// List of files in the sequence its loaded.
        /// </summary>
        private ArrayList filesList = new ArrayList();
        
        /// <summary>
        /// Gets the configuration.
        /// </summary>
        /// <returns>
        /// Xml document containing the entire Sitecore configuration.
        /// </returns>
        public ArrayList GetConfigFileSequence()
        {
            if (filesList.Count > 0)
                return filesList;

            XmlNode configNode = Sitecore.Configuration.ConfigReader.GetConfigNode();
            Assert.IsNotNull(configNode, "Could not read Sitecore configuration.");
            XmlDocument xmlDocument1 = new XmlDocument();
            xmlDocument1.AppendChild(xmlDocument1.ImportNode(configNode, true));
            filesList.Add("web.config");
            ExpandIncludeFiles(xmlDocument1.DocumentElement, new Hashtable());
            LoadAutoIncludeFiles();
            ReplaceGlobalVariables(xmlDocument1.DocumentElement);
            return filesList;
        }

        /// <summary>
        /// Loads the auto include files.
        /// </summary>
        /// <param name="patcher">
        /// The patcher.
        /// </param>
        /// <param name="folder">
        /// The folder.
        /// </param>
        private void LoadAutoIncludeFiles( string folder)
        {
            Assert.ArgumentNotNull(folder, "folder");
            try
            {
                if (Directory.Exists(folder))
                {
                    string[] files = Directory.GetFiles(folder, "*.config");
                    for (int i = 0; i < (int)files.Length; i++)
                    {
                        string str = files[i];
                        try
                        {
                            if ((int)(File.GetAttributes(str) & FileAttributes.Hidden) == 0)
                            {
                                filesList.Add(StripDriveInfo(str));
                            }
                        }
                        catch (Exception exception)
                        {
                            object[] objArray = new object[] { "Could not load configuration file: ", str, ": ", exception };
                            Log.Error(string.Concat(objArray), typeof(FactoryEx));
                        }
                    }
                    string[] directories = Directory.GetDirectories(folder);
                    for (int j = 0; j < (int)directories.Length; j++)
                    {
                        string str1 = directories[j];
                        try
                        {
                            if ((int)(File.GetAttributes(str1) & FileAttributes.Hidden) == 0)
                            {
                                LoadAutoIncludeFiles(str1);
                            }
                        }
                        catch (Exception exception1)
                        {
                            object[] objArray1 = new object[] { "Could not scan configuration folder ", str1, " for files: ", exception1 };
                            Log.Error(string.Concat(objArray1), typeof(FactoryEx));
                        }
                    }
                }
            }
            catch (Exception exception2)
            {
                object[] objArray2 = new object[] { "Could not scan configuration folder ", folder, " for files: ", exception2 };
                Log.Error(string.Concat(objArray2), typeof(FactoryEx));
            }
        }

        /// <summary>
        /// Strips the drive info and converts to virtual path.
        /// </summary>
        /// <param name="filePath">
        /// The file path which may or maynot have the drive information.
        /// </param>
        private string StripDriveInfo(string filePath)
        {
            string result = "";
            int foundIndex = filePath.IndexOf(@"app_config", StringComparison.InvariantCultureIgnoreCase);
            if (foundIndex >= 0)
                result = filePath.Substring(foundIndex);
            else
                result = filePath;

            return result.Replace(@"", "/").ToLowerInvariant();
        }

        /// <summary>
        /// Replaces global variables.
        /// </summary>
        /// <param name="rootNode">
        /// The root node.
        /// </param>
        private void ReplaceGlobalVariables(XmlNode rootNode)
        {
            Assert.ArgumentNotNull(rootNode, "rootNode");
            XmlNodeList xmlNodeLists = rootNode.SelectNodes(".//sc.variable");
            StringDictionary stringDictionary = new StringDictionary();
            foreach (XmlAttribute attribute in rootNode.Attributes)
            {
                string name = attribute.Name;
                string[] value = new string[] { attribute.Value };
                string str = StringUtil.GetString(value);
                if (name.Length <= 0)
                {
                    continue;
                }
                stringDictionary[string.Concat("$(", name, ")")] = str;
            }
            for (int i = 0; i < xmlNodeLists.Count; i++)
            {
                string attribute1 = XmlUtil.GetAttribute("name", xmlNodeLists[i]);
                string str1 = XmlUtil.GetAttribute("value", xmlNodeLists[i]);
                if (attribute1.Length > 0)
                {
                    stringDictionary[string.Concat("$(", attribute1, ")")] = str1;
                }
            }
            if (stringDictionary.Count == 0)
            {
                return;
            }
            ReplaceGlobalVariables(rootNode, stringDictionary);
        }

        /// <summary>
        /// Replaces global variables.
        /// </summary>
        /// <param name="node">
        /// The root node.
        /// </param>
        /// <param name="variables">
        /// The variables.
        /// </param>
        private void ReplaceGlobalVariables(XmlNode node, StringDictionary variables)
        {
            Assert.ArgumentNotNull(node, "node");
            Assert.ArgumentNotNull(variables, "variables");
            foreach (XmlAttribute attribute in node.Attributes)
            {
                string value = attribute.Value;
                if (value.IndexOf('$') < 0)
                {
                    continue;
                }
                foreach (string key in variables.Keys)
                {
                    value = value.Replace(key, variables[key]);
                }
                attribute.Value = value;
            }
            foreach (XmlNode childNode in node.ChildNodes)
            {
                if (childNode.NodeType != XmlNodeType.Element)
                {
                    continue;
                }
                ReplaceGlobalVariables(childNode, variables);
            }
        }

        /// <summary>
        /// Replaces the variables.
        /// </summary>
        /// <param name="value">
        /// The value.
        /// </param>
        /// <param name="node">
        /// The Xml node.
        /// </param>
        /// <param name="parameters">
        /// The parameters.
        /// </param>
        /// <returns>
        /// The replace variables.
        /// </returns>
        private string ReplaceVariables(string value, XmlNode node, string[] parameters)
        {
            Assert.ArgumentNotNull(value, "value");
            Assert.ArgumentNotNull(node, "node");
            node = node.ParentNode;
            while (node != null && node.NodeType == XmlNodeType.Element && value.IndexOf("$(", StringComparison.InvariantCulture) >= 0)
            {
                foreach (XmlAttribute attribute in node.Attributes)
                {
                    string str = string.Concat("$(", attribute.LocalName, ")");
                    value = value.Replace(str, attribute.Value);
                }
                value = value.Replace("$(name)", node.LocalName);
                node = node.ParentNode;
            }
            if (parameters != null)
            {
                for (int i = 0; i < (int)parameters.Length; i++)
                {
                    value = value.Replace(string.Concat("$(", i, ")"), parameters[i]);
                }
            }
            return value;
        }

        /// <summary>
        /// Loads the auto include files.
        /// </summary>
        /// <param name="element">
        /// The element.
        /// </param>
        private void LoadAutoIncludeFiles()
        {
            LoadAutoIncludeFiles(MainUtil.MapPath("/App_Config/Sitecore/Components"));
            LoadAutoIncludeFiles(MainUtil.MapPath("/App_Config/Include"));
        }

        
        /// <summary>
        /// Expands the include files embedded in the configuration document.
        /// </summary>
        /// <param name="rootNode">
        /// The root node.
        /// </param>
        /// <param name="cycleDetector">
        /// The cycle detector.
        /// </param>
        private void ExpandIncludeFiles(XmlNode rootNode, Hashtable cycleDetector)
        {
            Assert.ArgumentNotNull(rootNode, "rootNode");
            Assert.ArgumentNotNull(cycleDetector, "cycleDetector");
            if (rootNode.LocalName == "sc.include")
            {
                ExpandIncludeFile(rootNode, cycleDetector);
                return;
            }
            XmlNodeList xmlNodeLists = rootNode.SelectNodes(".//sc.include");
            for (int i = 0; i < xmlNodeLists.Count; i++)
            {
                ExpandIncludeFile(xmlNodeLists[i], cycleDetector);
            }
        }

        /// <summary>
        /// Gets the attribute.
        /// </summary>
        /// <param name="name">
        /// The attribute name.
        /// </param>
        /// <param name="node">
        /// The Xml node.
        /// </param>
        /// <param name="parameters">
        /// The parameters.
        /// </param>
        /// <returns>
        /// The attribute.
        /// </returns>
        public string GetAttribute(string name, XmlNode node, string[] parameters)
        {
            Assert.ArgumentNotNullOrEmpty(name, "name");
            Assert.ArgumentNotNull(node, "node");
            string attribute = XmlUtil.GetAttribute(name, node);
            return ReplaceVariables(attribute, node, parameters);
        }

        /// <summary>
        /// Expands the include file represented by a single configuration element (node).
        /// </summary>
        /// <param name="xmlNode">
        /// The XML node.
        /// </param>
        /// <param name="cycleDetector">
        /// The cycle detector.
        /// </param>
        private void ExpandIncludeFile(XmlNode xmlNode, Hashtable cycleDetector)
        {
            Assert.ArgumentNotNull(xmlNode, "xmlNode");
            Assert.ArgumentNotNull(cycleDetector, "cycleDetector");
            string lowerInvariant = GetAttribute("file", xmlNode, null).ToLowerInvariant();
            if (lowerInvariant.Length == 0)
            {
                return;
            }
            object[] objArray = new object[] { lowerInvariant };
            Assert.IsTrue(!cycleDetector.ContainsKey(lowerInvariant), "Cycle detected in configuration include files. The file '{0}' is being included directly or indirectly in a way that causes a cycle to form.", objArray);
            XmlDocument xmlDocument = XmlUtil.LoadXmlFile(lowerInvariant);
            if (xmlDocument.DocumentElement == null)
            {
                return;
            }

            filesList.Add(StripDriveInfo(lowerInvariant));

            XmlNode parentNode = xmlNode.ParentNode;
            XmlNode xmlNodes = xmlNode.OwnerDocument.ImportNode(xmlDocument.DocumentElement, true);
            parentNode.ReplaceChild(xmlNodes, xmlNode);
            cycleDetector.Add(lowerInvariant, string.Empty);
            ExpandIncludeFiles(xmlNodes, cycleDetector);
            cycleDetector.Remove(lowerInvariant);
            while (xmlNodes.FirstChild != null)
            {
                parentNode.AppendChild(xmlNodes.FirstChild);
            }
            foreach (XmlNode childNode in xmlNodes.ChildNodes)
            {
                parentNode.AppendChild(childNode);
            }
            XmlUtil.TransferAttributes(xmlNodes, parentNode);
            parentNode.RemoveChild(xmlNodes);
        }

    }
}

3. A Class for the code behind for the aspx file

using Sitecore.Configuration;
using Sitecore.sitecore.admin;
using System;
using System.Collections;
using System.Web;
using System.Web.UI;
using System.Xml;

namespace YOURNAMESPACE
{
    public class ConfigSequence : AdminPage
    {
        public ConfigSequence()
        {
        }

        /// <summary>
        /// Handles the Load event of the Page control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="T:System.EventArgs" /> instance containing the event data.</param>
        protected void Page_Load(object sender, EventArgs e)
        {
            base.CheckSecurity();
            FactoryEx factory = new FactoryEx();
            ArrayList filesList = factory.GetConfigFileSequence();
            
            for (int i = 0; i < filesList.Count; i++)
            {
                base.Response.Write(string.Format("{0}: {1}<BR>", (i+1).ToString(), filesList[i]));
            }
        }
    }
}

And here is the output:

1: web.config
2: /app_config/prototypes.config
3: /app_config/prefetch/common.config
4: /app_config/prefetch/core.config
5: /app_config/prefetch/common.config
6: /app_config/prefetch/master.config
7: /app_config/prefetch/common.config
8: /app_config/prefetch/webdb.config
9: /app_config/fieldtypes.config
10: /app_config/xamlsharp.config
11: /app_config/languagedefinitions.config
12: /app_config/mimetypes.config
13: /app_config/commands.config
14: /app_config/icons.config
15: /app_config/portraits.config
16: /app_config/include/.sitecore.speak.important.config
17: /app_config/include/datafolder.config
18: /app_config/include/sitecore.analytics.automation.timeoutprocessing.config
19: /app_config/include/sitecore.analytics.compatibility.config
20: /app_config/include/sitecore.analytics.config
21: /app_config/include/sitecore.analytics.conversion.config
22: /app_config/include/sitecore.analytics.excluderobots.config
23: /app_config/include/sitecore.analytics.marketingtaxonomy.config
24: /app_config/include/sitecore.analytics.model.config
25: /app_config/include/sitecore.analytics.mongodb.config
26: /app_config/include/sitecore.analytics.outcome.config
27: /app_config/include/sitecore.analytics.processing.aggregation.config
28: /app_config/include/sitecore.analytics.processing.aggregation.processingpools.config
29: /app_config/include/sitecore.analytics.processing.aggregation.services.config
30: /app_config/include/sitecore.analytics.processing.config
31: /app_config/include/sitecore.analytics.processing.services.config
32: /app_config/include/sitecore.analytics.reporting.config
33: /app_config/include/sitecore.analytics.tracking.aggregation.config
34: /app_config/include/sitecore.analytics.tracking.config
35: /app_config/include/sitecore.analytics.tracking.database.config
36: /app_config/include/sitecore.analytics.tracking.robotdetection.config
37: /app_config/include/sitecore.anticsrf.config
38: /app_config/include/sitecore.apps.taginjection.config
39: /app_config/include/sitecore.buckets.config
40: /app_config/include/sitecore.contentsearch.analytics.config
41: /app_config/include/sitecore.contentsearch.config
42: /app_config/include/sitecore.contentsearch.defaultconfigurations.config
43: /app_config/include/sitecore.contentsearch.lucene.defaultindexconfiguration.config
44: /app_config/include/sitecore.contentsearch.lucene.index.analytics.config
45: /app_config/include/sitecore.contentsearch.lucene.index.core.config
46: /app_config/include/sitecore.contentsearch.lucene.index.master.config
47: /app_config/include/sitecore.contentsearch.lucene.index.web.config
48: /app_config/include/sitecore.experienceeditor.config
49: /app_config/include/sitecore.experienceeditor.speak.requests.config
50: /app_config/include/sitecore.experienceexplorer.config
51: /app_config/include/sitecore.experienceexplorer.speak.requests.config
52: /app_config/include/sitecore.itemwebapi.config
53: /app_config/include/sitecore.marketing.client.config
54: /app_config/include/sitecore.marketing.config
55: /app_config/include/sitecore.marketing.definitions.marketingassets.repositories.config
56: /app_config/include/sitecore.marketing.definitions.marketingassets.repositories.lucene.index.master.config
57: /app_config/include/sitecore.marketing.definitions.marketingassets.repositories.lucene.index.web.config
58: /app_config/include/sitecore.marketing.definitions.marketingassets.repositories.lucene.indexconfiguration.config
59: /app_config/include/sitecore.media.requestprotection.config
60: /app_config/include/sitecore.mvc.config
61: /app_config/include/sitecore.mvcanalytics.config
62: /app_config/include/sitecore.mvcexperienceeditor.config
63: /app_config/include/sitecore.mvcsimulator.config
64: /app_config/include/sitecore.pathanalyzer.client.config
65: /app_config/include/sitecore.pathanalyzer.config
66: /app_config/include/sitecore.pathanalyzer.processing.config
67: /app_config/include/sitecore.pathanalyzer.services.config
68: /app_config/include/sitecore.segmentbuilder.config
69: /app_config/include/sitecore.services.client.config
70: /app_config/include/sitecore.shell.marketingautomation.config
71: /app_config/include/sitecore.speak.anticsrf.sheerui.config
72: /app_config/include/sitecore.speak.applications.config
73: /app_config/include/sitecore.speak.config
74: /app_config/include/sitecore.speak.itemwebapi.config
75: /app_config/include/sitecore.speak.launchpad.config
76: /app_config/include/sitecore.speak.mvc.config
77: /app_config/include/sitecore.webdav.config
78: /app_config/include/synthesis.config
79: /app_config/include/channel/sitecore.analytics.channel.config
80: /app_config/include/contenttesting/sitecore.contenttesting.config
81: /app_config/include/contenttesting/sitecore.contenttesting.intelligence.config
82: /app_config/include/contenttesting/sitecore.contenttesting.lucene.indexconfiguration.config
83: /app_config/include/contenttesting/sitecore.contenttesting.mvc.config
84: /app_config/include/contenttesting/sitecore.contenttesting.processing.aggregation.config
85: /app_config/include/experienceanalytics/sitecore.experienceanalytics.aggregation.config
86: /app_config/include/experienceanalytics/sitecore.experienceanalytics.client.config
87: /app_config/include/experienceanalytics/sitecore.experienceanalytics.reduce.config
88: /app_config/include/experienceanalytics/sitecore.experienceanalytics.storageproviders.config
89: /app_config/include/experienceanalytics/sitecore.experienceanalytics.webapi.config
90: /app_config/include/experienceprofile/sitecore.experienceprofile.client.config
91: /app_config/include/experienceprofile/sitecore.experienceprofile.config
92: /app_config/include/experienceprofile/sitecore.experienceprofile.reporting.config
93: /app_config/include/fxm/sitecore.fxm.bundle.config
94: /app_config/include/fxm/sitecore.fxm.config
95: /app_config/include/fxm/sitecore.fxm.lucene.index.domainssearch.config
96: /app_config/include/fxm/sitecore.fxm.speak.config
97: /app_config/include/listmanagement/sitecore.listmanagement.client.config
98: /app_config/include/listmanagement/sitecore.listmanagement.config
99: /app_config/include/listmanagement/sitecore.listmanagement.lucene.index.list.config
100: /app_config/include/listmanagement/sitecore.listmanagement.lucene.indexconfiguration.config
101: /app_config/include/listmanagement/sitecore.listmanagement.services.config
102: /app_config/include/simpleinjector/simpleinjector.config
103: /app_config/include/social/sitecore.social.config
104: /app_config/include/social/sitecore.social.experienceprofile.config
105: /app_config/include/social/sitecore.social.facebook.config
106: /app_config/include/social/sitecore.social.googleplus.config
107: /app_config/include/social/sitecore.social.linkedin.config
108: /app_config/include/social/sitecore.social.lucene.index.master.config
109: /app_config/include/social/sitecore.social.lucene.index.web.config
110: /app_config/include/social/sitecore.social.lucene.indexconfiguration.config
111: /app_config/include/social/sitecore.social.profilemapping.facebook.config
112: /app_config/include/social/sitecore.social.profilemapping.googleplus.config
113: /app_config/include/social/sitecore.social.profilemapping.linkedin.config
114: /app_config/include/social/sitecore.social.profilemapping.twitter.config
115: /app_config/include/social/sitecore.social.socialmarketer.config
116: /app_config/include/social/sitecore.social.twitter.config

Let me know if you have any questions or comments.