Implementing redirects when upgrading a site to SharePoint

Late last month we launched the best site i’ve ever had the pleasure of working on, Education + Training International (ETI). The pure size and scope of the site, particularly everything which drives it behind the scenes, is something which everyone who has worked on (and will continue to work on) it should be proud of. Having taken the site from inception to launch, the number of hurdles we came across along the way would be enough to fill this blog for the year. This post will touch on just one small aspect of it – ensuring that the URLs from the old site accurately redirected to those in the new.

I actually touched on this topic in some sense a couple of years ago in my post 301 vs 302 Redirects in SharePoint so it was good to get the opportunity to revisit it again, explore options and implement a solution. This post is quite long, so I’ve broken it up into headings outlining what is being discussed – feel free to skip ahead to what interests you.

Requirements and Caveats

There were 2 main considerations for requiring the redirects (and we wanted all URLs redirected – there wasn’t a ridiculous number we’d need to handle – around 130 or so). First and perhaps most importantly was user experience. The last thing I wanted was for our users to be consistently hitting a generic 404 page. The other consideration was for Search Engine Optimisation – ETI had previously invested in SEO on their existing site and we did not want to lose those search engine gains which had been realised.

There was a caveat to the decision making process (isn’t there always). The infrastructure team was not prepared to take on the burden of managing the redirects and had reservations anyway around the feasibility of managing them at the reverse proxy level.

I also personally wanted to avoid any manual deployment steps both out of principle and for disaster recovery and future maintenance reasons – those kinds of steps are often lost over time as people leave and documentation becomes outdated and ignored.

Finally, ideally the process would be able to be managed down the track, preferably through a SharePoint list within the site.

Rejected Solution #1 – custom web part on PageNotFound

My first investigation centred around the concept of placing a custom web part on the 404 PageNotFound page (having easy, editable access to this page from within SharePoint is a great improvement over previous versions). The idea was that a list would exist which managed the old URLs and the mapping to the new URL, if the ?requestUrl= found a match, we’d serve up a 301 redirect to the new URL, otherwise we’d provide the next best thing – perhaps a search within the site for some of the terms within the URL. The user experience was actually seemless with this approach, however a quick look at Fiddler showed that the 404 response was still served before my code hijacked it and served up the 301, meaning the search engines would assume the pages were gone. Back to the drawing board.

Rejected Solution #2 – storing URL rewrite mappings in a separate file

It was at this point that I gave up on my desire to have a user-editable list of redirects (perhaps prematurely). Our specific requirement was simply to redirect the old URLs to the new, future redirections were more of a nicety I was trying to provide. I knew a bit about managing redirects in IIS (I referenced Jeremy Thake’s post How we did it: 301 Redirects in IIS 7.5 on Windows Server 2008 R2 in my previous blog post) so figured I’d explore that track (I did have some reservations about blowing out the web.config particularly in relation to the 250kb file size cap, so I intended to use Ruslan’s Storing URL rewrite mappings in a separate file – I figured this would also allieviate the ‘adminstrative burden’ of managing them through IIS). The approach worked great – I could manage a set of old URL/new URL pairs in a separate configuration file, but to avoid the manual deployment steps I’d want it managed and deployed via our solution.

Unfortunately, that separate file had to exist along side the web.config in the IIS virtual directory for the site. I wasn’t able to reference an absolute path and deploy my configuration file somewhere within the SharePoint layouts directory. I investigated the ability to Deploy Files to SharePoint Web Application Virtual Directories At Feature Activation via Brian Jackett but the noted flaws in that system and the complexity around it quickly turned me off. Back to the drawing board.

Implemented Solution – writing URL rewrite mappings to web.config via feature

My next approach was to take a look at managing the redirects within the web.config itself. I did have concerns regarding blowing out the file and the maximum size limit for it – but tests soon proved that we wouldn’t even get close to that figure, so it wasn’t as much of a concern as I had made it out to be. So this approach worked fine too, the challenge was to remove the manual administration/deployment steps out of the process. Enter Using SPWebConfigModification to Update the Web.config in SharePoint 2013. I think i’ve explained how to do that reasonably well in that post (which i’ve also now updated to include some new information) so i’ll jump straight into some code snippets you can use in combination with that post.

            SPWebConfigModification modification = new SPWebConfigModification();
            modification.Path = "configuration/system.webServer";
            modification.Name = "rewrite";
            modification.Sequence = 0;
            modification.Owner = "ETIWebConfigModifications";
            modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
            modification.Value = "<rewrite />";
            webApp.WebConfigModifications.Add(modification);

            modification = new SPWebConfigModification();
            modification.Path = "configuration/system.webServer/rewrite";
            modification.Name = "rewriteMaps";
            modification.Sequence = 0;
            modification.Owner = "ETIWebConfigModifications";
            modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
            modification.Value = "<rewriteMaps />";
            webApp.WebConfigModifications.Add(modification);

            modification = new SPWebConfigModification();
            modification.Path = "configuration/system.webServer/rewrite/rewriteMaps";
            modification.Name = "rewriteMap";
            modification.Sequence = 0;
            modification.Owner = "ETIWebConfigModifications";
            modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
            modification.Value = "<rewriteMap name='Redirects' />";
            webApp.WebConfigModifications.Add(modification);

            modification = new SPWebConfigModification();
            modification.Path = "configuration/system.webServer/rewrite/rewriteMaps/rewriteMap";
            modification.Name = "redirect-1";
            modification.Sequence = 0;
            modification.Owner = "ETIWebConfigModifications";
            modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
            modification.Value = "<add key='/eti-overview/profile-of-education-training-international.html' value='/about-eti' />";
            webApp.WebConfigModifications.Add(modification);

            modification = new SPWebConfigModification();
            modification.Path = "configuration/system.webServer/rewrite";
            modification.Name = "rules";
            modification.Sequence = 0;
            modification.Owner = "ETIWebConfigModifications";
            modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
            modification.Value = "<rules />";
            webApp.WebConfigModifications.Add(modification);

            modification = new SPWebConfigModification();
            modification.Path = "configuration/system.webServer/rewrite/rules";
            modification.Name = "rule";
            modification.Sequence = 0;
            modification.Owner = "ETIWebConfigModifications";
            modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
            modification.Value = "<rule name='Redirect rule1 for Redirects' />";
            webApp.WebConfigModifications.Add(modification);

            modification = new SPWebConfigModification();
            modification.Path = "configuration/system.webServer/rewrite/rules/rule";
            modification.Name = "match";
            modification.Sequence = 0;
            modification.Owner = "ETIWebConfigModifications";
            modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
            modification.Value = "<match url='.*' />";
            webApp.WebConfigModifications.Add(modification);

            modification = new SPWebConfigModification();
            modification.Path = "configuration/system.webServer/rewrite/rules/rule";
            modification.Name = "conditions";
            modification.Sequence = 0;
            modification.Owner = "ETIWebConfigModifications";
            modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
            modification.Value = "<conditions />";
            webApp.WebConfigModifications.Add(modification);

            modification = new SPWebConfigModification();
            modification.Path = "configuration/system.webServer/rewrite/rules/rule/conditions";
            modification.Name = "add";
            modification.Sequence = 0;
            modification.Owner = "ETIWebConfigModifications";
            modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
            modification.Value = "<add input='{Redirects:{REQUEST_URI}}' pattern='(.+)' />";
            webApp.WebConfigModifications.Add(modification);

            modification = new SPWebConfigModification();
            modification.Path = "configuration/system.webServer/rewrite/rules/rule";
            modification.Name = "action";
            modification.Sequence = 0;
            modification.Owner = "ETIWebConfigModifications";
            modification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
            modification.Value = "<action type='Redirect' url='{C:1}' appendQueryString='false' />";
            webApp.WebConfigModifications.Add(modification);

The only concerns I had left were around the performance implications of managing so many redirects within IIS/the web.config, but they were soon allayed via How to check for performance in URL Rewriting and IIS Rewrite Module rewrite map performance.

Was there a better option?

In the end I was pretty happy with the end result. I managed to take the burden off the infrastructure team (activating a web-app scoped feature is no significant task), managed to take the manual administrative steps (such as deploying files to specific locations which would need to be repeated if new WFE’s were added to the farm) out of the process and provided a seemless experience for the users while maintaining good SEO practices. The only thing I didn’t achieve was providing the user the ability to manage the redirects and I couldn’t help wonder if that was possible.

Thankfully I have the pleasure of working with someone who’s seen it all before and had a chance to try a few different approaches to this problem over time! Faced with this same challenge (but at a much larger scale), his solution was to create a custom IIS module which inspected the 404 requests going to the redirect page and converted them to 301’s, then a custom web part on that page did another 301 if it found an item in the old-new URL list, otherwise it wrote 404 to the response header for the nicely branded 404 error page.

The only downside to this approach I could see was the need to install the custom IIS module on each server – however is that really worse than having to install the IIS rewrite module on each server? Probably not. The approach also had a significant unexpected upside – by monitoring the analytics on the 404 page they were able to identify legacy links which were still being used across the web, and had the ability to redirect those to a new URL on the fly and provide ongoing functionality to redirect merged/renamed/moved pages on the ever evolving web site.

So hopefully this post has given you a few ideas regarding how to implement your redirects when upgrading a legacy site into SharePoint. As always there are pro’s and con’s to each approach but there are definitely options available to achieve a decent result to meet a number of different requirements.

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.

Integrating Twitter into SharePoint

It’s been over a year since I wrote about Integrating WordPress into SharePoint as one of my first (and most visited) posts on this blog, so it seemed only fitting that early into the new year I found myself faced with the task of integrating both Twitter and Facebook into SharePoint 2013. As it was when it came to WordPress, the integration to be discussed here is light – simply displaying a feed of the latest tweets on the site – but the hurdles encountered are likely to be helpful and relevant with any level of integration you’d be capable of achieving with a bit of study of the Twitter API and third party SDKs.

Thankfully there was a lot of information available on the net in regards to programming against Twitter, the first source being the Twitter API itself. It wasn’t long after that I learnt that there were mature and decent SDKs available to make programming against Twitter even easier – one of the .NET ones recommended by Twitter themselves was Spring.NET Social for Twitter which proved easy to learn, integrate into the solution and use.

The first thing to know about creating an application for Twitter is that you must register it. Creating a new application will give you the Consumer key and Consumer secret you’ll need to identify your app, and creating an access token will give you the Access token and Access token secret to authorise your application to interact with your feed. I chose to store these values as configuration items using the list approach identified in my post Application Settings in SharePoint.

The second thing to know is that the Twitter API is rate limited (180 accesses per 15 minutes for user feeds) and therefore needs to be considered. This is easily countered by caching the results which are returned – obviously not ideal considering the results returned would not be up-to-date to the minute however necessary if you believe the site will be accessed that many times within a 15 minute period.

So the next step was to code up a proof of concept, but as we all know, approaching any task within the confines of SharePoint tends to lead to unexpected hurdles that need to be overcome. The first issue I encountered was ‘No connection could be made because the target machine actively refused it’. This turned out to be a bit of a red herring – the target machine had nothing to do with the problem, it was in fact a replica of the proxy issue I came across when integrating WordPress and was resolved in the same way. The error encountered and the web.config entry to resolve it can be seen below.

proxy-error

<system.net>
  <defaultProxy>
    <proxy proxyaddress="http://your-proxy-address:port" bypassonlocal="true" />
  </defaultProxy>
</system.net>

Thinking that was all a bit too easy and looking forward to seeing the end result I was immediately fronted with another issue: ‘The remote certificate is invalid according to the validation procedure’ which could be further narrowed down to ‘The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel’. This one took a little longer to get over as there were a lot of posts on the net which proposed unresponsive or unattractive resolutions to the problem. There were however a few ways in the end that this one could be resolved.

certificate-error

Firstly, it was possible to set a web.config entry to get SharePoint to ignore the problem altogether – however this did not seem like the most security conscious way to approach it.

<system.net>
    <settings>
      <servicePointManager
          checkCertificateName="false"
          checkCertificateRevocationList="false"
      />
    </settings>
</system.net>

Secondly, it was possible to get SharePoint to ignore the problem via code – basically the same approach as above and similarly unattractive.

ServicePointManager.ServerCertificateValidationCallback = new System.Net.Security.RemoteCertificateValidationCallback(delegate { return true; });

Finally, and probably the most appropriate and security conscious approach to take, was to register the SSL certificate as a trusted certificate in SharePoint. This is where it took a bit of trial and error to find the right one – most guides suggested that trusting the right root certificate from VeriSign would do the trick however it wasn’t until I exported the appropriate certificate from https://api.twitter.com that I got it working – it’s important to note I needed to export the VeriSign Class 3 Secure Server CA – G2 certificate, not the root. You can read up on how to export the certificate in Sean Wallbridge’s post SharePoint 2010 and Cert Trust – Could not establish trust relationship for the SSL/TLS secure channel and either use his Central Administration instructions listed or the following PowerShell commands:

$root = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2("(location)\VeriSign Class 3 Secure Server CA - G2.cer")
New-SPTrustedRootAuthority -Name "VeriSign Class 3 Secure Server CA - G2.cer" -Certificate $root

So after all that I had a working twitter integration – however the feed only returned the CreatedAt date and not the relative time lapsed since the tweet was made. I used a couple of posts to implement that feature, namely Sam Allen’s C# Pretty Date and a thread in StackOverflow Calculating relative time.

The end result and some code snippets to achieve it can be seen below – overall the experience of creating an integration application with Twitter was relatively pain free and easy to accomplish which is more I can say for my experience with integrating Facebook!

twitter-integration

 ITwitter twitter = new TwitterTemplate(ConsumerKey, ConsumerSecret, AccessToken, AccessTokenSecret);

 IList<Tweet> tweets = twitter.TimelineOperations.GetUserTimeline(2);
 foreach (Tweet tweet in tweets) tweetDisplays.Add(new TweetDisplay(tweet));

 // Cache the result for 20 minutes (Twitter limit access for a certain number of calls per 15 minutes)
 CachingHelper.AddToCache(cacheKey, tweetDisplays, new TimeSpan(0, 20, 0));

 TweetsRepeater.DataSource = tweetDisplays;
 TweetsRepeater.DataBind();

Application Settings in SharePoint

One of the most commonly neglected features in SharePoint I find across a number of different projects is the manner in which application settings are managed. Far too often it is left to manually modifying the <AppSettings> section of the web.config file for the relevant web application. Perhaps this is due to legacy behaviour of ASP.NET developers moving into the SharePoint sphere, perhaps its due to environments being small and the risks of manually modifying the file not being exposed. It may be due to organisational process or procedure, or (most likely) it may be because on face value it appears to be the most simple, quick and easy option to maintain some configuration values.

It’s ironic that in projects laced with features and solutions to deploy resources ‘the right way’ that configuration is overlooked and remains a manual process. Aside from wanting to avoid any manual modification of files directly on the server by principle, dangers exist in terms of ensuring the web.config files are the same on all web front end servers. Maintaining application settings this way also requires access to the web.config file (and hence server) itself which wouldn’t be considered best practice, and the act of changing a variable in the web.config directly would cause the application pool to refresh and cause the site to load slowly on the next hit.

I write this post with a tinge of regret – I too have been guilty on multiple occassions of maintaining the status quo and leaving (and even adding to) configuration values maintained manually in a web.config file. So not only should this post serve as a source of information regarding the best practice methods to maintain application settings, but it should also serve as a motivator to myself and others to practice what I/we preach on all future greenfield projects.

Anyone who has studied for or taken the application development exam (70-576) would have noticed that 4 main options consistently get presented for application configuration: web.config, lists, property bags and the hierarchical object store. I’ll discuss each option briefly and provide some situations where the method would be appropriate.

Web.config

Now it may seem like i’m beginning to contradict myself here – I previously mentioned that manually maintaining application settings in the web.config was a BAD idea. It is. The beauty of SharePoint though is that it provides a mechanism to maintain application settings in the web.config file automatically via the SPWebConfigModification class. If you’re wanting to store them in the web.config, then a feature should be created which on activation uses the SPWebConfigModification class to modify the web.config for you. This ensures that the same values are written to the web.config files on all front-end web servers and even ensures they’re consistent amongst new servers added to the farm.

You’re likely to want to use this option if you just can’t pull yourself away from the web.config for variable configuration. At least its a safer method. There are some valid reasons however – it’s reasonably simple to incorporate this functionality and then easy to program with seeing you’re probably used to it already. It ensures values are consistent across all front-end web servers and are available and consistent across the entire web application. It’s also fairly useful for ‘set and forget’ the values – if you’re unlikely to need to change them in the future, then you won’t run into the application pool refresh issue I mentioned previously and will get the performance benefits of the cached settings.

Before running off and using this method however, be warned that the web is littered with issues and problems with using SPWebConfigModification. I’d probably want to avoid making any ‘unnecessary’ changes to the web.config especially for the purposes of setting configuration values considering there are other options available.

SharePoint lists

It almost seems too easy and obvious. SharePoint provides us with these useful things called lists. Most of us would be familiar with or have used database-driven variable configuration mechanisms before. Lists, on face value, are reasonably similar to database tables. Why don’t we use lists for variable configuration? It’s not actually that silly a proposition and is in fact one of the more reasonable options. One of the beauties of using lists is that you get all that SharePoint listy goodness. Versioning, security and an out-of-the-box UI for example. You’re likely already familiar with list-based programming, so that isn’t too much of an inconvenience.

Using SharePoint lists is definitely one of the preferred methods. It’s probably easier to outline some of the reasons why you may not want to use it; a site with lax security restrictions would probably be wise to avoid this method – you wouldn’t want any user being able to modify or delete configuration items or the list itself. If you wanted the application settings to be available to multiple site collections/web applications then you’d potentially end up duplicating the list values across these sites.

There’s another huge advantage to choosing this method. An MVP named Chris O’Brien has written a Config store framework available on Codeplex which makes the option even more attractive and erases some of the drawbacks from choosing it. I’d definitely recommend reading a bit about it in his post A better Config Store for SharePoint sites.

Property bags

This option is perhaps a little less obvious but similarly powerful. A number of SharePoint objects contain a Properties property which stores a collection of key/value pairs. These obviously lend themselves towards being used for storing application settings.

This option is possibly the best one to harness when scope is an important consideration for configuration. It lends itself to being able to maintain numerous variables with different values on a per-scope basis (per web, per web application and so forth).

Like the SharePoint list option, this also benefits from addons which improve the usability and effectiveness of the option. The SharePoint Guidance Library provides the Application Setting Manager which is an API to read and write application settings. Codeplex also has the SharePoint Property Bag Settings project which provides a GUI in Central Administration to manage the settings.

Hierarchical Object Store

This is a farm-scoped store similar to a property bag. It requires custom code to create and is far more complex than the other options, however has the benefit of being able to store more complex types if that’s neccessary. Generally for the purpose of variable configuration though, in my opinion i’d go for one of the other options.

You’d possibly choose this option if you were looking for a global storage option for values or required complex values to be stored. If the values are simple key/value pairs however a farm-scoped property bag would probably be easier to use and do the trick.

WebPart properties

I know I said before there were 4 main options, but I wanted to briefly cover this one too. In the event that your custom web parts require configuration values specific to the web part then it will often make sense to include custom properties editable in the web part itself – they are then able to be set by modifying the settings of the web part.

My initial concept for this post was to include an array of advantages, disadvantages and code samples on each option to more completely highlight where each could be used and how they’re used, however during my research for this post came across an MSDN article written by David Mann titled Managing Custom Configuration Options for a SharePoint Application – it essentially covers in reasonable detail those points and I would strongly recommend reading.

Overall I’d have to say I prefer the SharePoint list method the best. I’m somewhat surprised, I remember thinking a while ago that using lists for application settings would be a bit primitive and that surely the other options which have been provided (obviously for the purpose of variable configuration?) like SPWebConfigModification, SPPersistedObject and the Properties collections would be the best practice method to use. I guess sometimes the easy option actually is the best, and it’s sandbox-friendly to boot.