Nodes with namespace uri:s when modifying web.config file in SharePoint

A while back I wrote a post on how to use a real web.config file when writing modifications to SharePoint. Apparently I had missed that if a node (like the one you see below) has a namespace reference you have to handle that in a special way, thanks to Rikard Uppström for pointing that out.

So, since there is reference to a namespace uri the XPath expression pointing to this node has to be a bit special (again Rikard pointed me in the right direction).

The code for the new method look like this:

private void HandleElementsWithNamespaces()
{
 List<XElement> ToRemove = new List<XElement>();

 var NodesWithNamespaces = from attribute in FindConfigurationNodes().Attributes()
                           where attribute.IsNamespaceDeclaration
                           let node = attribute.Parent
                           select new
                           {
                             OrginalNode = node,
                             NamespaceUri = attribute.Value,
                             WithoutNamespace = node.RemoveNamespace(attribute.Name.LocalName)
                            };

 foreach (var node in NodesWithNamespaces)
 {
   log.WriteLine("Found element '{0}' which has namespace '{1}'. This needs special handling.", node.OrginalNode.Name.LocalName, node.NamespaceUri);

   var path = string.Format("{0}/*[namespace-uri()='{1}' and local-name()='{2}']",
                      BuildXPathFrom(node.OrginalNode),
                      node.NamespaceUri,
                      node.WithoutNamespace.Name.LocalName);

    log.WriteLine("Rewrote path as '{0}'.", path);

    var children = from child in node.WithoutNamespace.Elements()
                   where child.HasAttributes == false
                   select child;

    log.WriteLine("Number of childnodes to add: {0}", children.Count());

    foreach (var child in children)
    {
      log.WriteLine("Handling childnode '{0}'", child.Name.LocalName);
      var firstNode = child.Elements().FirstOrDefault();

      if (firstNode == null)
      {
        log.WriteLine("Section '{0}' has no elements.", child.Name.LocalName);
        continue;
      }

      var firstAttribute = firstNode.FirstAttribute;

      if (firstAttribute == null)
      {
        log.WriteLine("Section '{0}' has no attributes.", child.Name.LocalName);
        continue;
      }

      SPWebConfigModification modification = new SPWebConfigModification
      {
        Path = path,
        Sequence = sequence++,
        Owner = owner,
        Name = string.Format("*[namespace-uri()='{0}' and local-name()='{1}']/*[namespace-uri()='{0}' and local-name()='{2}'][@{3}='{4}']/parent::*",
                       node.NamespaceUri,
                       child.Name.LocalName,
                       firstNode.Name.LocalName,
                       firstAttribute.Name.LocalName,
                       firstAttribute.Value),
        Value = child.ToString(SaveOptions.DisableFormatting)
       };


       if (application.WebConfigModifications.Any(Config => Config.Equals(modification)))
       {
          log.WriteLine("The modification '{0}' has already been added to the collection of modifications", modification.Name);
          continue;
       }

       log.WriteLine("Adding modication('{1}') '{0}' to the collection of modifications", modification.Name, modification.Type.ToString());
       application.WebConfigModifications.Add(modification);
    }

    // remove the contents from the xml so it doesn´t get handled again
    ToRemove.Add(node.OrginalNode);
  }

 ToRemove.ForEach(node =>
 {
 log.WriteLine("Removing node '{0}' from the in-memory representation of the config file. ", node.Name.LocalName);
 node.Remove(); // remove from parent
 });

 }

Let´s go through some of the code. First a linq expression finds all attributes that are namespace declarations and I extract some useful information into an anonymous type that I use later in the method.
A new extension method (RemoveNamespace) is used to remove that namespace attribute otherwise LinqToXml will include that namespace in all child elements, that method is shown below.
When the linq expression finds an attribute that is a namespace it will find the parent node of that attribute (which in this case is <runtime>) and we will build the path from there.
This method adds childnodes (not sections) so next we will go through all childnodes that do not have any attributes (which really is a section but we will not bother with that).
Next we construct SPWebConfigModifications from these “sections” (childnodes). The tricky part is the XPath expressions which have to be correct in order for SharePoint to find each individual node based on the first attribute on the first element.
Note that we actually use the entire concatenated “inner xml” of each of the childnodes and in this method we do not add them individually.

The last thing to notice is that we have to “remove” (only in memory) the namespace node (in this example it is <assemblyBinding>) has to be removed from the “big” config file before we continue with the “normal” parsing.

public static XElement RemoveNamespace(this XElement element, string namespaceAttribute)
return XElement.Parse(Regex.Replace(element.ToString(), String.Format(@"({0}:?[^=]*=[""][^""]*[""])", namespaceAttribute), string.Empty,
RegexOptions.IgnoreCase | System.Text.RegularExpressions.RegexOptions.Multiline));
}

Below is an excerpt from the SharePoint web.config file showing that the changes are indeed appended correctly.

I have also updated the demo code.

Download the code –>

SPWebConfigModification

, , , , , , ,

  1. #1 by Sachin on August 6, 2010 - 20:14

    How to add/remove comments () section using modifications. Thanks

  2. #2 by Sachin on August 6, 2010 - 20:16

    How to add/remove comments (<!– –>) section using modifications. Thanks

  1. Trying to make web.config modifications maintainable « Johan Leino

Leave a reply to Sachin Cancel reply