Geeks With Blogs
Amusingly MOSS ...It's funny how difficult some stuff is when it really shouldn't be

Every ASP.NET developer is accustomed to writing configurable values into the web.config for directing programming logic at runtime.  This practice has always made a lot of sense for storing things that will change depending where assets (such as databases, web services, etc.) are going to live come production time.

The tricky part comes in when you need to store configurable values that change the behavior of your code, but don't apply to other systems in any meaningful way.  An example of one such configuration setting would be a magic number that is meaningful to determine the state of a piece of data in your system, but is entirely useless to the rest of the world. 

While you can get away with storing values (such as magic numbers) in the web.config for most standard ASP.NET applications, doing so with SharePoint is a major no-no.  SharePoint can overwrite sections of the web.config not supported by WSP/features when solution deployments are made, and you don't want to run the risk of your values being wiped out.  It also goes without saying that the less cluttered you can make the web.config, the better - it's complicated enough as it is.

So, if not in the web.config, how and where should you store configurable values for your custom SharePoint code, and how do you got about fetching it safely and efficiently? 

It takes two components:

  • An XML document (stored in a known location - a document library is ideal) to store the configuration settings
  • A class to cache, and then fetch the values at runtime

Simple enough, eh?  Here's an example of what your XML document should look like (You'll notice that it looks identical to the <appSettings> node in a standard web.config file):

<?xml version="1.0" encoding="utf-8"?>
<appSettings>
  <add key="FeedCacheTime" value="300" />
  <add key="DataInProcessId" value="92" />
  <add key="BuyoutThreshold" value="42" />
</appSettings>

And here's the code for fetching the values at runtime (please note where you are to supply where you store the XML configuration document you created in the previous step):

using System;
using System.Collections.Specialized;
using System.Configuration;
using System.Xml;
using Microsoft.SharePoint;

namespace Utilities
{
    public static class CustomConfigurationManager
    {
        private static NameValueCollection _configurationItems;

        static CustomConfigurationManager()
        {
            LoadConfiguration();
        }

        private static void LoadConfiguration()
        {
            XmlDocument configurationDocument = new XmlDocument();
           
            string portalUri = string.Empty;
            string relativeUrl = string.Empty;
            string requestUrl = string.Empty;
            string xmlString = string.Empty;

            SPSecurity.CodeToRunElevated secureCode = null;
            
            if (secureCode == null)
            {
                secureCode = delegate
                {
                    portalUri = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority).ToLower();
                    relativeUrl = GetRelativeUrl(URL TO CONFIGURATION XML FILE);
                    requestUrl = portalUri + relativeUrl;

                    using (SPWeb web = new SPSite(requestUrl).OpenWeb())
                    {
                        Uri uri = new Uri(requestUrl);
                        SPFile file = web.GetFile(uri.AbsolutePath);
                       
                        if (file != null)
                        {
                            byte[] bytes = file.OpenBinary();
                            xmlString = Encoding.ASCII.GetString(bytes);
                            bytes = null;
                            configurationDocument.LoadXml(xmlString);
                        }
                        else
                        {
                            xmlString = string.Empty;
                        }
                    }
                };
            }
            SPSecurity.RunWithElevatedPrivileges(secureCode);

            LoadAppSettingsCollection(configurationDocument);
        }

        private static void LoadAppSettingsCollection(XmlDocument configurationDocument)
        {
            _configurationItems = new NameValueCollection();

            XmlNodeList configNodes = configurationDocument.GetElementsByTagName("add");
           
            foreach (XmlNode node in configNodes)
            {
                string key = null;
                string value = null;

                if (!string.IsNullOrEmpty(node.Attributes["key"].Value))
                {
                    key = node.Attributes["key"].Value;
                }

                if (!string.IsNullOrEmpty(node.Attributes["value"].Value))
                {
                    value = node.Attributes["value"].Value;
                }

                if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
                {
                    _configurationItems.Add(key, value);
                }
            }
        }

        private static string GetRelativeUrl(string url)
        {
            StringBuilder builder = new StringBuilder(url.ToLower());
            if (url.ToLower().IndexOf("https://") > -1)
            {
                builder.Replace("https://", string.Empty);
                builder.Remove(0, builder.ToString().IndexOf("/"));
            }
            else if (url.ToLower().IndexOf("http://") > -1)
            {
                builder.Replace("http://", string.Empty);
                builder.Remove(0, builder.ToString().IndexOf("/"));
            }
            return builder.ToString();
        }

        public static NameValueCollection AppSettings
        {
            get { return _configurationItems; }
        }
    }
}

To access your configuration settings, you'd call it like this:

string feedCacheTime = Utilities.CustomConfigurationManager.AppSettings["FeedCacheTime"];

This methodology accomplishes a few things:

  1. The call to the configuration is almost identical to getting it out of web.config (no real learning curve to implement)
  2. The configuration file is only loaded once per IIS session, so you don't have the overhead of lots of calls to the configuration document library, nor do you waste time waiting for it to load something that isn't going to change very frequently
  3. You "shield" the rest of the world from your code-specific configuration (good encapsulation practice)
  4. You don't have to worry about SharePoint wiping your configuration due to other solution deployment changes.

 

Posted on Thursday, March 5, 2009 3:52 PM SharePoint | Back to top


Comments on this post: SharePoint Configuration Values Best Practices

# re: SharePoint Configuration Values Best Practices
Requesting Gravatar...
Why not use the SPWeb.Properties property?

I don't like the idea of some config file stored somewhere. You know what it's for, but does the server admin know?

How to deploy that file to multiple frontend servers? Is it part of a solution package?

Cheers,
Wes
Left by Wesley on Mar 09, 2009 5:03 AM

# re: SharePoint Configuration Values Best Practices
Requesting Gravatar...
Great questions, Wes – thanks for reading, and taking the time to comment.

My understanding of the SPWeb.Properties bag is that it’s scoped for a single web – if this is true, wouldn’t you have to create an instance of that SPWeb in memory just to get the configuration setting if the context of your custom code is outside the scope of that site?

In the end, you can go back and forth forever on where you want to be efficient vs. "easy" in your code/configuration. As the system architect, it’s your job to make that determination. In this case, I’ve opted for ease of use in code (easier to read/maintain) over something that could perhaps be approached with a little more "elegance."

As for your question about how to deploy the configuration file, absolutely – the xml file would be part of a solution so that it would be deployed to all WFE’s on your farm. Ideally, you’d have production settings pre-configured in your WSP.

Cheers,
Adam
Left by Adam McKee on Mar 09, 2009 1:55 PM

Your comment:
 (will show your gravatar)


Copyright © Adam McKee | Powered by: GeeksWithBlogs.net