Introduction
When I first bumped into SPWebConfigModification
and began researching it, I was amazed at how much confusion was expressed in blogs and articles about how to use this SharePoint class for programmatically making changes to web.config files. After working with it a bit, I'm no longer surprised at the confusion... It is typically used in a Feature receiver, but very awkward to test from a Feature receiver, so I wrote this tool (a web part) to make that easier. In this article, I've tried to highlight many of the usual stumbling blocks, and explain some of the not-so-obvious usage details.
Background
What's SPWebConfigModification?
In a nutshell, it is a collection of changes to be made to
web.config files, created using the SharePoint Object Model, to be applied to all Web front end servers. There is minimal amount of official documentation, and I found myself doing an awful lot of digging and experimenting to find out how to use it. In fact, it can make you nuts at first! I learned most from reading articles and blog posts, and to all of those authors, many thanks - especially to
Vincent Rothwell,
Reza Alirezaei,
Mark Wagner,
DTerrell. My hope is that combining collected wisdom with new insights and providing a tool for exploration will save the reader much time. We'd better start at the beginning...
A SharePoint Web application is basically the same thing as an IIS web application. SharePoint extends this with the idea of zones. SharePoint Zones (Default, Intranet, Internet, Extranet, Custom) are like different doors opening into the same room- they provide different kinds of access to the same content. Each SharePoint Zone extension is implemented as a separate IIS web site, with its own web.config file. So, one SharePoint Web application may include several IIS web sites – and therefore several web.config files.
Why is that important? Applying the modification collection affects all web.config files for a Web application, but not uniformly- there are quirks. A SharePoint Web application maintains a collection of these modifications in the SharePoint configuration database. The SPWebService
class is used to apply the Web application’s modification collection to all Web front ends by scheduling a timer job.
Typically this is used in a Feature receiver, adding web.config changes during Feature activation, and removing them during Feature deactivation, so everything stays tidy. One quirk that pops up here is that removal of a modification is only applied in the Default Zone, leaving all the other zones untouched. Another quirk is that if different changes are needed for different zones – say for a custom Role Provider - you can't do it – all zones get the same XML.
How does this basically work? Create an instance of the SPWebConfigModification
class, and set 6 properties. Add as many of these modifications as needed to the Web application’s modification collection with the Add
(modification) method, then call SPWebApplication.Update()
to save the collection. Call SPWebService.ContentService.ApplyWebConfigModifications()
to propagate the changes to the configuration database and WFEs. Removing is similar - find the modification(s) in the Web application’s collection using the Owner
property, call the collection’s Remove
(modification) method, then update and apply as before. There are tricky aspects to these 6 properties, so beware! Name and Value property definitions change according to the modification Type. Also, according to MSDN, "you must be an administrator on the front-end Web server for your code to work."
SPWebConfigModification Properties
EnsureChildNode
- The modification will add/remove child node(s)
EnsureAttribute
- The modification will ensure the value of a particular attribute EnsureSection
- The modification will ensure that a section exists and is a singleton
Path
- An XPath
expression that locates the parent node being operated on in this modification
Name
- Depends on the Type
- For
EnsureAttribute
and EnsureSection
, the attribute name or section name as a valid XML name (basically alphanumeric plus a few punctuation marks) - For
EnsureChildNode
, an XPath
expression that uniquely identifies the child node(s) to be inserted as a child of the 'Path
' parent node.
Owner
- Do not leave this blank, this is critical for identifying modifications to be removed. Choose wisely. Choose something unique - maybe the Feature name or Id.
Sequence
- This only applies when there are multiple modifications of the same type for the same node or attribute; otherwise it is ignored. If it is used, modifications will be applied in ascending order. Valid range: 0 to 65536. Not used in this article.
Value
- Depends on the Type
- For
EnsureChildNode
, the child node(s) to be added/removed - For
EnsureAttribute
, the value of the attribute - For
EnsureSection
, the name of the section
Using the Tool
The tool is a web part created on a MOSS 2007 development box, using WSPBuilder
for deployment (http://www.codeplex.com/wspbuilder). For a brief explanation of .wsp files, and deploying them, see Building and deploying SharePoint Solution packages. It is intended to be used to troubleshoot modifications before using them, and to help understand how the process works. As a best practice, create a throwaway Web application on a development machine, extend it as much as needed for testing purposes, and point the tool there. Modifications can be created that break web sites… or even corrupt the database... all of which we're trying to avoid!
EnsureChildNode Example – A Single Node (the "Hello, World" example)
Select a Web application (use the Change/Set Test WebApp link at the top), preferably one set up expressly for testing modifications.
To add child node
<add key="AcademicYear" value="2009-2010" />
to parent node
configuration/appSettings
Fill in the properties like this: (Note – If you use copy & paste, be careful with the quotes- if smart quotes are pasted into the textbox, they create errors.)
Text version of properties:
Path | configuration/appSettings |
Name | add[@key="AcademicYear"] |
Owner | AcademicFeature |
Sequence | 0 |
Type | EnsureChildNode |
Value | <add key="AcademicYear" value="2009-2010" /> |
Click Create and validate modification
Messages are displayed just below the Value textbox, with a dashed border. Basic validation will be attempted. Path will be checked for correct XPath syntax, and whether the node exists in the Default Zone web.config. Name will be checked for correct XPath syntax if an EnsureChildNode
Type, or valid XML otherwise. Owner will be flagged if it is blank. Value will be tested to see if the XML is well-formed if an EnsureChildNode
Type, and valid XML otherwise.
Click Add current modification to local collection
This will create the modification as well as add it, and if all goes well, the modification will then appear in the right pane. Modifications are listed in the order they are found in the collection, with the first one at the top of the list.
Click Save modification collection
This will call SPWebApplication.Update()
for the current Web application; the collection visible in the right pane is the entire collection as accessed through code.
Click Apply modifications to Web Front-ends
Calls SPWebService.ContentService.ApplyWebConfigModifications()
, which starts a timer job. If all goes well, a Modifications applied message will display, and all web.configs for the selected Web application will have the new node(s), typically just before the end tag of the parent node. In this case, a search for </appSettings>
should help find the new < add key="AcademicYear" value="2009-2010" />
child node quickly. The configuration database is updated as well.
Click the show/hide link for the Default Zone web.config.
(I am design-challenged, so please bear with the web.config displays.) The browser’s Find-on-this-page tool will be helpful in finding the new node – the web.configs must be shown before the browser can search them, so expand the ones of interest. Here’s the right pane of the tool showing the new child node in the Default Zone web.config:
Configuration Database
Just for fun, let’s look in the SharePoint configuration database. A query like this displays a list that appears to be the modification collection. (Caution- I am guessing here.)
USE SharePoint_Config_Nancy
SELECT [O1].[Name]
,[O1].[Properties]
,[O2].[Name] ParentName
,[Classes].[FullName] ParentClassName
,[O1].[ParentId]
,[O1].[Status]
,[O1].[Version]
FROM [SharePoint_Config_Nancy].[dbo].[Objects] O1
LEFT OUTER JOIN
[SharePoint_Config_Nancy].[dbo].[Objects] O2
ON [O1].[ParentId] = [O2].[Id]
LEFT OUTER JOIN
[SharePoint_Config_Nancy].[dbo].[Classes] Classes
ON [O2].[ClassId] = [Classes].[Id]
WHERE ([O1].[Name] LIKE '%WebConfigChanges%')
ORDER BY [O1].[Name]
Right click in the Properties cell for the Web application of interest, choose Copy from the context menu, and paste the results into Notepad. In Notepad, save the XML with a friendly name like SharePoint3616.xml, and then view it in Internet Explorer.
Well there it is, I think – along with a bunch of other nodes that don't show up in code. Some articles I read commented that the modifications seem to be applied alphabetically, sorted according to Path+Name. Not sure how to verify this, but since the XML here appears to be a serialization of a SortedList
, with the sort field composed of Path+Name, I'm inclined to think that is the case. And this could be very handy - more on that later.
Closing the fld elements gives this view – note the 3 names:
Back to the Tool
Click Remove modification(s) for current owner from local collection.
The screen refreshes, and now there are no modifications found for this Web application. What about in the database? It is still there! What about the web.config? Still there too! Oops- we never clicked Save modification collection to update the Web application, and we did not click Apply modifications to Web Front-ends.
Let’s try again to actually remove the entry. Add it back. Remove. Update. Apply. Now it is gone from the database. The Default Zone web.config no longer has the AcademicYear
element, but the Extranet and Internet Zones do. It worked, in its quirky fashion.
What about Clear local modification collection? Use with caution. This removes all entries, just as usual for any collection. If the empty collection is Updated and Applied to the WFEs, then all the modifications for that Web application will be removed. If there were modifications from a number of different Features, this could break some applications. But since this is pointed at a throwaway test application (right?) - it’s ok to experiment with it.
EnsureChildNode Example - A nodeset
What about adding the XML for a Membership
provider? It looks something like this:
<membership defaultProvider="SPListUser">
<providers>
<add name="SPListUser" type="
FBA.MembershipProvider.SPListUser,
FBA.MembershipProvider,
Version=1.0.0.0, Culture=neutral,
PublicKeyToken= 8383efad02158ca5"
applicationName="/" />
</providers>
</membership>
This could be added one node at a time:
Path | configuration/system.web |
Name | membership[@defaultProvider="SPListUser"] |
Owner | MembershipFeature |
Sequence | 0 |
Type | EnsureChildNode |
Value | <membership defaultProvider="SPListUser" /> |
Path | configuration/system.web/membership[@defaultProvider="SPListUser"] |
Name | providers |
Owner | MembershipFeature |
Sequence | 0 |
Type | EnsureChildNode |
Value | <providers /> |
Path | configuration/system.web/membership[@defaultProvider="SPListUser"]/providers |
Name | add[@name="SPListUser"] |
Owner | MembershipFeature |
Sequence | 0 |
Type | EnsureChildNode |
Value | <add name="SPListUser" type="FBA.MembershipProvider.SPListUser, FBA.MembershipProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken= 8383efad02158ca5" applicationName="/" /> |
Or all-at-once like this:
Path | configuration/system.web |
Name | membership[@defaultProvider="SPListUser"] |
Owner | MembershipFeature |
Sequence | 0 |
Type | EnsureChildNode |
Value | <membership defaultProvider="SPListUser"> <providers> <add name="SPListUser" type="FBA.MembershipProvider.SPListUser, FBA.MembershipProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken= 8383efad02158ca5" applicationName="/" /> </providers> </membership> > |
How to Make the Removal Fail (or "How This Can Make You Nuts")
For many developers, XPath is learned on an as-needed basis; that is, learning just enough to accomplish the current task, so the ability to detect slight inconsistencies may not be well developed. If we change the Name
property on the all-at-once version of the membership provider modification above by just one character- delete the "@
" so that it becomes membership[defaultProvider="SPListUser"]
, it will still validate, and it will still add the nodeset. Not only will it add the nodeset, it will keep adding it when modifications are applied, so that multiple copies are in the web.config, resulting in a 'PARSER ERROR MESSAGE: Sections must only appear once per config file' error.
Worse, the removal now fails! So while it is critical to use the Owner
property to identify the modifications for removal, it is also critical that Path+Name correctly identify the node(s) in the Value property. There are many excellent XPath tutorials and references; here’s one of my favorites: http://www.w3schools.com/xpath/ (I usually keep an XPath tutorial or two open in the browser when working with SPWebConfigModification
.)
EnsureSection Example - What Exactly is a Section? (and More on Sorting)
A number of blog posts mentioned that sections cannot be removed by the removal method - but first, what is a section? Thanks to DTerrell, it looks like a section is an element node with no attributes that is not the innermost child node, or a child of a child node. I suspect EnsureSection
comes in handy when needing to be sure parent nodes exist exactly once. (If you try adding a child node to a non-existent parent, it fails.) I tried adding a connectionStrings
section as an EnsureSection
Type, removing it- and it was still there. EnsureSection
additions are permanent. But as long as EnsureSection
modifications are used only for standard sections, that shouldn't be a problem- child nodes can always be removed, empty sections do no harm.
What if there are 2 or more modifications to add the same section? Only one section is created, so the singleton aspect seems true.
Remember the type=SortedList
when looking at the database? If the modifications are sorted into order according to Path+Name, then the modifications will be in the correct order to build the parent nodes in the correct sequence. Looking at the 3 "fld"s in the database, if EnsureSection
modifications are applied first, then EnsureChildNode
, then EnsureAttribute
, all sorted by Path+Name- then parent nodes will be built before child nodes are added, child nodes added before attributes are edited. This is a guess. I tried a few experiments to test this. The following experiment adds parent nodes one at a time with EnsureSection
, and a child node with EnsureChildNode
, but adds them to the modification collection out of order, with Sequence=0 for all.
In the following example, if they are sorted and applied as expected, then this XML should be added as a child of the configuration/system.web node:
<webParts>
<personalization>
<authorization>
<allow users="*" verbs="modifyState" />
</authorization>
</personalization>
</webParts>
Here is the collection (the authorization element is added before the personalization element, and the child node is before all that – totally out of order):
This works!
Another example, using EnsureChildNode
only, adding the nodes out of order, and attempting to add the same node twice. Note second attempt has a different "duration" attribute value of 120. When multiple Features each add modifications to the collection, this scenario is quite possible.
This worked, and only added the nodeset one time, with the duration attribute set to 120. I think it is clear that sorting is happening.
So why would one ever want to use EnsureSection
rather than EnsureChildNode
? The 2 outstanding characteristics seem to be that EnsureSection
guarantees the node will only be created once, and that it cannot be removed. When using EnsureChildNode
, it is possible for the same node to be created multiple times, resulting in a broken web site.
EnsureAttribute Example
A typical web.config has many appSettings
entries, like this:
Let’s change the value of the FeedCacheTime
to 450
. First identify the target node in the Path
, and remember Name
and Value
depend on the Type
- Name
is now the target attribute name, Value
the attribute value to be assigned - like this:
Path | configuration/appSettings/add[@key="FeedCacheTime"] |
Name | value |
Owner | FeedCacheAttributeFeature |
Sequence | 0 |
Type | EnsureAttribute |
Value | 450 |
Don't forget that XML is case sensitive. The first time I tried this, I keyed in "Value
" for the Name
(note the capital "V"), and it added a second attribute, named "Value
", with a value of 450
. The result looked like this:
<add key="FeedCacheTime" value="300" Value="450" />
Oops.
Tried it with Name = “value”
and it worked - result:
<add key="FeedCacheTime" value="450" />
Whew.
Miscellaneous Valuable Quotes
From http://msdn.microsoft.com/en-us/magazine/cc507633.aspx
SafeControl and CAS entries
For Web Part SafeControl entries and custom access control policies, rely on the SharePoint solution packaging framework.
Manually editing web.config when SPWebConfigModifications are in use
Manually removing or changing the entries in web.config that are managed by SharePoint won't do you any good in the long term. SharePoint tracks these entries and ensures that they are in web.config each time the WSS service restarts.
From http://www.crsw.com/mark/Lists/Posts/Post.aspx?ID=32
Development vs Production
It is important to remember that when developing and testing on a single server installation of SharePoint there are many activities that are able to complete instantly. However, when that same section of code executes on a SharePoint farm with multiple servers, the time it takes to complete a given task is longer. This is due to timer jobs and farm synchronization events that must occur.
For this reason, you should only execute the ApplyWebConfigModifications()
method only once within a feature activation or event handler. If more than one call to the ApplyWebConfigModifications()
method is made in close succession you will likely get the following error: A web configuration modification operation is already running.
[applying changes across the Farm – this code works]
myWebApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
Note: This code did not work across the farm (I could not tell you why).
SPFarm.Local.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
Coda
In summary, SPWebConfigModification
can be difficult to work with. The definition of the Name
and Value
properties vary according to the Type
property, XML and XPath expressions must be precise, requiring a good bit of general knowledge about both, and general usage requirements are still a bit murky. I hope that the tool will make testing modifications and understanding SPWebConfigModification
easier and faster. Happy coding!