Getting multiple layers of web.config modifications to live together
In our SharePoint 2010 solutions we have web.config modifications that are standard, and custom modifications that are later added on top of the standard ones. This article describes a way for multiple levels of modifications to live together, in a way that supports upgrades of the standard modifications without destroying the custom ones.
Before you read this you should be familiar with the web config modification framework in SharePoint. It’s important never to modify SharePoint web.config files directly, especially when you’re deploying your solutions to a farm environment.
Let’s say the standard web.config modifications made by our solution look like this:
<configuration>
<ourproduct>
<standard-element>
<sub-element/>
</standard-element>
</ourproduct>
</configuration>
.. and then somebody adds an attribute and a node to this for a particular customer, like this:
<configuration>
<ourproduct>
<standard-element custom-property=”x”>
<sub-element/>
</standard-element>
<custom-element/>
</ourproduct>
</configuration>
.. and now we want to update the standard section like this:
<configuration>
<ourproduct>
<standard-element custom-property=”x”/>
<custom-element/>
<another-standard-element/>
</ourproduct>
</configuration>
We’ve removed one element, and added another. And we want to deploy this to all installations where our solution is in use, without knowing which (or if) custom elements may have been added.
Using Sequence and Owner
As long as we only add new nodes, and never remove old ones, we could simply add new web config modifications to SharePoint. But I’m going to describe a solution that I think is cleaner, and also allows you to delete some of the standard elements.
The key is the Sequence and Owner properties of the SPWebConfigModification class. Owner is used to keep track of who owns a modification, and should be the same for all modifications added by a particular feature. That way, when the feature deactivates, it can just loop through all the web config modifications, and delete the ones with that name.
Sequence tells SharePoint in which order to apply modifications. If you don’t set a Sequence, the order depends on the order you added the modifications in, which becomes fragile if you want to upgrade a feature later. In our scenario, the standard changes should have a low Sequence, for instance 0, and the custom changes a higher Sequence, such as 10. That way, when the web.config is updated, the custom changes are always added on top of the standard ones.
(There’s no point in having different Sequence values for each modification in your feature, as they’re always applied in the order you add them.)
Activation and deactivation
So let’s begin with the initial activation. We have a feature called StandardFeatureX and another called CustomFeatureY. When each feature activates, they add their modifications:
SPWebConfigModification mod = new SPWebConfigModification(“ourproduct”, “configuration);
mod.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
mod.Value = “<ourproduct><standard-element><sub-element/></standard-element></ourproduct>”;
mod.Owner = “StandardFeatureX”;
mod.Sequence = 0; // 10 for the custom feature
webApp.WebConfigModifications.Add(mod);
And when they are deactivated, they delete them, checking the Owner property to make sure they remove everything:
List<SPWebConfigModification> modsToDelete = new List<SPWebConfigModification>();
foreach (SPWebConfigModification existingMod in webApp.WebConfigModifications)
{
if (existingMod.Owner == “StandardFeatureX”)
{
modsToDelete.Add(existingMod);
}
}
foreach (SPWebConfigModification modToDelete in modsToDelete)
{
webApp.WebConfigModifications.Remove(modToDelete);
}
After the modifications have been added or removed, we apply the modifications, like this:
webApp.Update();
webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
So if we first activate StandardFeatureX, and then CustomFeatureY, we get the result in the second example above. (We can then later deactivate the features in the opposite order to return web.config to its initial state.)
Upgrading the standard modifications
But now we want to change StandardFeatureX so that we get the result in the third example above. How do we do that? If you simply deactivate and activate StandardFeatureX, SharePoint won’t know what to do with the modifications from CustomFeatureY in the mean time. And we can’t deactivate CustomFeatureY, because we don’t know that it exists.
The solution is to use a feature upgrade, as described in an earlier post, and then in the FeatureUpgrading method do the following:
[Remove all our old modifications]
[Add all our modifications back again, the way they're supposed to look now]
webApp.Update();
webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
The trick is to not apply the modifications until the end, when we’re done. SharePoint does not update web.config files immediately when you add or remove a modification. It waits until you call ApplyWebConfigModifications. So it’s okay that the first step leaves the modifications in an invalid state, as long as it’s made valid again immediately afterwards.
And when you do call ApplyWebConfigModifications, the modifications are made in the order of their Sequence values first, and the order they were added second, like this:
Sequence 0, added first
Sequence 0, added second
Sequence 10, added first
Sequence 10, added second
It’s still possible that the standard changes will conflict with the custom changes, of course. You can only remove elements that are not open for customization. But this approach allows you make non-conflicting upgrades that might otherwise be difficult, and it allows you to have a few big chunks of XML that you add new elements to, instead of making every change as a separate modification.
Behind the scenes
When you’re working with web.config modifications it’s useful to look at the modifications that are currently in effect on a web application. Use this PowerShell command:
(get-spwebapplication http://mysite).WebConfigModifications | select Name, Path, Sequence, Owner
