Using SPWebConfigModification to Update the Web.config in SharePoint 2013

Seven months ago I wrote an article on Jumping the Hurdles of using SPWebConfigModification to Update the Web.config – that article was based on my experiences in SharePoint 2010 and having researched the topic thoroughly at the time I was interested to see how things fared in SharePoint 2013. Thankfully I had the opportunity to architect a solution from the ground up in 2013 which gave me the opportunity to once again adhere to the vow I made in Application Settings in SharePoint to never manually modify the web.config file.

While this post has not been as thoroughly researched at the time of writing as I usually would like, a quick search on SPWebConfigModification in SharePoint 2013 brought up few results and when I was looking into it for the project mentioned above, little had been written about it. A recent question on the OZMOSS mailing list showed that there were still some questions around how SPWebConfigModification did (or didn’t) work so I thought it would be useful to document my findings here for anyone wanting to ‘do the right thing’ and keep their hands off the web.config file.

For a bit of background for this post I’d thoroughly recommend you read the article I link to above – the purpose of this post is to compare the behaviour of the functionality between versions of the platform and it will make far more sense if you understand my (and others) previous findings.

However for those of you who care little about the past and just want to know what the deal is in 2013 – here is the quick summary:

SPWebConfigModification in SP2010 came with a number of issues however none which were completely insurmountable with a little work. The majority were already documented by others (and I link to them in that post) however one phenomenon had little written about it and even less in terms of a solution.

This problem was that even though the removal code ran successfully and even removed the entry from the modification collection, the element in the web.config file still physically existed. I came up with a workaround where  in the removal code I first applied another modification reverting the entry back to its original state, then ran the code to remove all modifications.

So how did this fair in SP2013? There was good news and bad news. On the plus side, this issue had been fixed! Removing the entry from the modification collection successfully removed the entry from the web.config file. On the negative side it came with a nasty side effect – if the modification you were removing was a change to an attribute on a pre-existing element then that whole element was removed from the web.config, not just your change.

This was clearly unacceptable behaviour particularly if the entry being removed was essential to successfully loading the website.

Once again however I managed to jump the hurdles that always seem to exist with this tricky class. The workaround this time was to remove all the customisations by default at the beginning of both the FeatureActivated and FeatureDeactivating functions and then add the customisation required. In FeatureActivated this would be your customised entry. In FeatureDeactivating this would be the original entry (if it existed in the web.config before your modifications). Again this solution isn’t full-proof and is prone to falling over in future iterations of the platform, however it is a solid workaround as things stand now. I’ve provided a code snippet to achieve this below:

public class WebConfigModificationsFeatureEventReceiver : SPFeatureReceiver
{
	// Due to what appears to be a bug or just and unfortunate side-effect in SP2013, using SPConfigModification on an existing element
	// replaces that element with a new one. When removing the customisation, that element is removed, hence meaning that the original
	// entry no longer exists. To counter this we will re-add the original values in the deactivating feature.

	public override void FeatureActivated(SPFeatureReceiverProperties properties)
	{
		SPWebApplication webApp = properties.Feature.Parent as SPWebApplication;
		RemoveAllCustomisations(webApp);

		#region Enable session state

		httpRuntimeModification = new SPWebConfigModification();
		httpRuntimeModification.Path = "configuration/system.web/pages";
		httpRuntimeModification.Name = "enableSessionState";
		httpRuntimeModification.Sequence = 0;
		httpRuntimeModification.Owner = "WebConfigModifications";
		httpRuntimeModification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureAttribute;
		httpRuntimeModification.Value = "true";
		webApp.WebConfigModifications.Add(httpRuntimeModification);

		httpRuntimeModification = new SPWebConfigModification();
		httpRuntimeModification.Path = "configuration/system.webServer/modules";
		httpRuntimeModification.Name = "add[@name='Session']";
		httpRuntimeModification.Sequence = 0;
		httpRuntimeModification.Owner = "WebConfigModifications";
		httpRuntimeModification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
		httpRuntimeModification.Value = "<add name='Session' type='System.Web.SessionState.SessionStateModule' preCondition='' />";
		webApp.WebConfigModifications.Add(httpRuntimeModification);

		#endregion

		/*Call Update and ApplyWebConfigModifications to save changes*/
		webApp.Update();
		webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
	}

	public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
	{
		SPWebApplication webApp = properties.Feature.Parent as SPWebApplication;
		RemoveAllCustomisations(webApp);

		#region Revert session state

		httpRuntimeModification = new SPWebConfigModification();
		httpRuntimeModification.Path = "configuration/system.web/pages";
		httpRuntimeModification.Name = "enableSessionState";
		httpRuntimeModification.Sequence = 0;
		httpRuntimeModification.Owner = "WebConfigModifications";
		httpRuntimeModification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureAttribute;
		httpRuntimeModification.Value = "false";
		webApp.WebConfigModifications.Add(httpRuntimeModification);

		#endregion

		/*Call Update and ApplyWebConfigModifications to save changes*/
		webApp.Update();
		webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
	}

	private void RemoveAllCustomisations(SPWebApplication webApp)
	{
		if (webApp != null)
		{
			Collection<SPWebConfigModification> collection = webApp.WebConfigModifications;
			int iStartCount = collection.Count;

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

				if (configMod.Owner == "WebConfigModifications")
				{
					collection.Remove(configMod);
				}
			}

			// Apply changes only if any items were removed.
			if (iStartCount > collection.Count)
			{
				webApp.Update();
				webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();
			}
		}
	}
}

So once again we’re left with an option to do things right which doesn’t quite act the way you’d expect, but as you can see, can still be used to achieve the end result desired. For all its ills in both versions of the products i’d still strongly recommend taking this approach to making changes to the web.config file over making manual modifications any day. I hope this post has made doing so a little easier moving forward.

UPDATES

We shouldn’t be surprised, but over time new hurdles have presented themselves which need documenting (and I apologise, the first one I mention I should have covered a long time ago when I encountered it!).

The approach documented above worked fine in my single-server development environment. As soon as we tried activating the feature in a multi-server farm test environment all sorts of issues occurred. This has been documented however and you can read about it in Jeremy Jameson’s post Waiting for SharePoint Web.config Modifications to Finish and Simon Doy’s post PowerShell to Detect Web Configuration Modification jobs.

The code changes required include a few functions to be added and a slight modification to the ApplyWebConfigModifications call as indicated in the below code.

        private static bool IsJobDefined(SPFarm farm)
        {
            SPServiceCollection services = farm.Services;

            foreach (SPService service in services)
            {
                foreach (SPJobDefinition job in service.JobDefinitions)
                {
                    if (string.Compare(job.Name, jobTitle, StringComparison.OrdinalIgnoreCase) == 0)
                        return true;
                }
            }

            return false;
        }

        private bool IsJobRunning(SPFarm farm)
        {
            SPServiceCollection services = farm.Services;

            foreach (SPService service in services)
            {
                foreach (SPRunningJob job in service.RunningJobs)
                {
                    if (string.Compare(job.JobDefinition.Name, jobTitle, StringComparison.OrdinalIgnoreCase) == 0)
                        return true;
                }
            }

            return false;
        }

        private void WaitForOneTimeJobToFinish(SPFarm farm)
        {
            float waitTime = 0;

            do
            {
                if (!IsJobDefined(farm) && !IsJobRunning(farm))
                    break;

                const int sleepTime = 500; // milliseconds

                Thread.Sleep(sleepTime);
                waitTime += (sleepTime / 1000.0F); // seconds

            } while (waitTime < 20);
        }

        /*Call Update and ApplyWebConfigModifications to save changes*/
        webApp.Update();
        WaitForOneTimeJobToFinish(webApp.Farm);
        webApp.Farm.Services.GetValue<SPWebService>().ApplyWebConfigModifications();

The second issue is one we’ve encountered more recently. The issue was in regards to the inability to successfully extend a web application which already had the web.config modifications applied. There’s 2 ways to approach this one – if you know before the fact, then make sure you extend your web application before applying any of the web.config modifications. Otherwise, hope that you’ve implemented decent deactivation code and be sure to deactivate the features before extending, and then reapply after the fact.

Advertisements

6 Responses to Using SPWebConfigModification to Update the Web.config in SharePoint 2013

  1. Pingback: Reporting Services w.SharePoint 2013 (SafeControls) | SharePointRoot

  2. Pingback: SPWebConfigModification and SharePoint 2013 | Jeroen's SharePoint Blog

  3. Filip Hurta says:

    Hi, what is the name of the job in SP2013? In the updated code there is a variable jobTitle, which is not defined..

  4. Saaffy says:

    Amazing how SharePoint is not able to do the most simple configration steps from within the object model. I have worked for almost 15 years with this product but it never stops to amaze me how buggie its code is.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: