Using the property bag of an SPWeb for storing configuration settings

Each web site in SharePoint 2007 has a property bag that can be accessed from the object model. This property bag is ideal for storing configuration settings for a web. I’m going to show you how to use this in combination with serializing a settings object and by using the Provider design pattern. The Provider pattern is in here so that you can also easily switch to storing the configuration settings in for example a SharePoint list.

I’m first going to outline the requirements for our solution:

  • A mechanism needs to be in place that allows for storing custom site configuration settings for a web;
  • Site collection wide settings should also be stored. These settings are different from the settings on a web site;
  • The storage location of these settings should be easily swappable;
  • The configuration settings should be accessible by using object properties;
  • The consumers of these settings should not have to care about retrieving and storing the settings;

Since the data that is stored for each type is different, a container class for each type is created. To define that they are both configuration setting objects, they both implement a public interface called “IConfigurationSettings”.

1
2
3
4
5
6
7
8
public interface IConfigurationSettings 
{ 
    SPWeb Web 
    { 
       get; 
       set; 
    } 
}

Both types of site settings are stored in a property bag of the corresponding web. Site collection settings are stored on the root web. You can also have any other settings type, just as long as they implement the interface above. In our example we have:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[Serializable()] 
public class SiteConfigurationSettings : IConfigurationSettings 
{ 
    [NonSerialized()] 
    public SPWeb Web 
    { 
        get; 
        set; 
    } 
}       
 
[Serializable()] 
public class SiteCollectionConfigurationSettings : IConfigurationSettings 
{ 
    [NonSerialized()] 
    public SPWeb Web 
    { 
        get; 
        set; 
    } 
}

For the sake of keeping this blog post brief, I’m not going to outline the different properties for each of these classes, I’m sure you can use your imagination. :)

The configuration values are persisted to the underlying data store by using the Provider design pattern. Should the storage location be changed in the future, for example to a custom list, this pattern can easily facilitate such a change. It would only require the development of a provider for the custom list. The container classes and management objects remain unchanged.

Each provider has to implement the interface “IConfigurationProvider”. This interface dictates that any implementing class should at least provide methods for retrieving and storing configuration settings. It uses generics to cope with the different kind of configuration classes. Although an interface could be returned here, generics make more sense since they immediately return the proper type, instead of an interface.

To ease the process of converting the stored data into usable objects and vice versa, the objects are serialized when they are persisted to the data store. Should any new configuration settings be added in the future, then no change to the data storage is required.

1
2
3
4
5
6
7
8
internal interface IConfigurationProvider 
{ 
    void Clear(SPWeb web);        
 
    T GetConfiguration<T>(SPWeb web) where T : IConfigurationSettings, new();        
 
    void SaveConfiguration<T>(SPWeb web, T settings); 
}

The following is the actual implementation used by the property bag provider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
internal class PropertyBagConfigurationProvider : IConfigurationProvider 
{ 
    private readonly string _propertyBagSettingsKey = "ConfigurationSettings";     
 
    #region IConfigurationProvider Members     
 
    public void Clear(SPWeb web) 
    { 
        web.Properties[_propertyBagSettingsKey] = null; 
        web.Properties.Update(); 
    }     
 
    public T GetConfiguration<T>(SPWeb web) where T : IConfigurationSettings, new() 
    { 
        if (web == null) 
        { 
            throw new ArgumentNullException("web"); 
        }     
 
        string propertyData = web.Properties[_propertyBagSettingsKey]; 
        T deserializedConfig = new T(); 
        deserializedConfig.Web = web;     
 
        if (!string.IsNullOrEmpty(propertyData)) 
        { 
            object obj = null;    
 
            using (StringReader sr = new StringReader(propertyData)) 
            { 
                byte[] b = Convert.FromBase64String(propertyData); 
                Stream stream = new MemoryStream(b);    
 
                try 
                { 
                    BinaryFormatter bf = new BinaryFormatter(); 
                    obj = (object)bf.Deserialize(stream); 
                } 
                catch 
                { 
                    throw; 
                }    
 
                deserializedConfig = (T)obj; 
            } 
        }     
 
        return deserializedConfig; 
    }     
 
    public void SaveConfiguration<T>(SPWeb web, T settings) 
    { 
        if (web == null) 
        { 
            throw new ArgumentNullException("web"); 
        }     
 
        if (settings == null) 
        { 
            throw new ArgumentNullException("settings"); 
        }     
 
        string result;    
 
        using (Stream stream = new MemoryStream()) 
        { 
            try 
            { 
                BinaryFormatter bf = new BinaryFormatter(); 
                bf.Serialize(stream, settings); 
            } 
            catch 
            { 
                throw; 
            }    
 
            stream.Position = 0; 
            byte[] b = new byte[stream.Length]; 
            stream.Read(b, 0, (int)stream.Length);    
 
            result = Convert.ToBase64String(b, 0, b.Length); 
        }    
 
        web.Properties[_propertyBagSettingsKey] = result; 
        web.Properties.Update(); 
    }     
 
    #endregion 
}

The provider objects are not exposed through the public API, since the calling client doesn’t necessarily have to be aware of how data storage and retrieval is done. To abstract this concept, a generic “Configuration” object is created. When an instance of this object is created, it also creates an instance of the configured provider. Any calls to the storage and retrieval methods are redirected to the provider.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class Configuration<T> where T : IConfigurationSettings, new() 
{ 
    #region Fields   
 
    private static Configuration<T> _instance; 
    private SPWeb _web = null; 
    private IConfigurationProvider _provider = null; 
    private T _settings = default(T);   
 
    #endregion   
 
    #region Properties   
 
    public T Settings 
    { 
        get 
        { 
            if (_provider != null &amp;&amp; _web != null) 
            { 
                _settings = _provider.GetConfiguration<T>(_web); 
            } 
            else 
            { 
                _settings = new T(); 
            }   
 
            return _settings; 
        } 
    }   
 
    #endregion   
 
    #region Constructors   
 
    internal Configuration() 
    {   
 
    }   
 
    internal Configuration(SPWeb web) 
    { 
        _web = web;   
 
        // Note that this can also be changed to read a setting from a configuration file so that it can be changed without touching the code. 
        _provider = Activator.CreateInstance(typeof(PropertyBagConfigurationProvider)) as IConfigurationProvider; 
    }   
 
    #endregion   
 
    #region Public Methods   
 
    public void Clear() 
    { 
        _provider.Clear(_web); 
    }   
 
    public void SaveSettings() 
    { 
        _provider.SaveConfiguration<T>(_web, _settings); 
    }   
 
    #endregion   
 
    #region Public Static Methods   
 
    internal static Configuration<T> GetConfigurationSettings(SPWeb web) 
    { 
        _instance = new Configuration<T>(web);   
 
        return _instance; 
    }   
 
    #endregion 
}

By using this Configuration class, we can abstract the behaviour of the provider and hide it from the caller. This way the caller doesn’t have to care about what’s going on with the data store, he only sees the methods to retrieve and save the configuration settings.

In order to improve usability of the application, a final wrapper class is created to provide easy access to each of the different settings. The wrapper class exposes properties for SiteConfigurationSettings and SiteCollectionConfigurationSettings. These properties will look for settings in the web SPContext.Current.Web. The exposed methods can be used to retrieve settings from a different web, or when you’re calling the code from a console application for example, where the SPContext does not exist.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public static class ConfigurationManager 
{ 
    public static SiteConfigurationSettings WebSettings 
    { 
        get 
        { 
            if (SPContext.Current != null &amp;&amp; SPContext.Current.Web != null) 
            { 
	    return ConfigurationManager.OpenSiteConfiguration(SPContext.Current.Web).Settings; 
            }   
 
             return null; 
        } 
    }   
 
    public static SiteCollectionConfigurationSettings SiteCollectionSettings 
    { 
        get 
        { 
            if (SPContext.Current != null &amp;&amp; SPContext.Current.Site != null) 
            { 
	    return ConfigurationManager.OpenSiteCollectionConfiguration(SPContext.Current.Site).Settings; 
            }   
 
            return null; 
        } 
    }   
 
    public static string CustomCssUrl 
    { 
        get 
        { 
            if (SPContext.Current != null &amp;&amp; SPContext.Current.Web != null) 
            { 
                return StyleConfiguration.CustomCssLocation + String.Format(StyleConfiguration.CustomCssFileFormat, SPContext.Current.Web.ID.ToString()); 
            }   
 
            return String.Empty; 
        } 
    }   
 
    public static Configuration<sitecollectionconfigurationsettings></sitecollectionconfigurationsettings> OpenSiteCollectionConfiguration(SPSite siteCollection) 
    { 
        if (siteCollection == null) 
        { 
            throw new ArgumentNullException("siteCollection"); 
        }   
 
        return OpenSiteCollectionConfiguration(siteCollection.RootWeb); 
    }   
 
    public static Configuration<sitecollectionconfigurationsettings></sitecollectionconfigurationsettings> OpenSiteCollectionConfiguration(SPWeb rootWeb) 
    { 
        if (rootWeb == null) 
        { 
            throw new ArgumentNullException("rootWeb"); 
        }   
 
        return Configuration<sitecollectionconfigurationsettings></sitecollectionconfigurationsettings>.GetConfigurationSettings(rootWeb); 
    }   
 
    public static Configuration<siteconfigurationsettings></siteconfigurationsettings> OpenSiteConfiguration(SPWeb web) 
    { 
        if (web == null) 
        { 
            throw new ArgumentNullException("web"); 
        }   
 
        if (web.IsRootWeb) 
        { 
            // Return an empty settings object, since this method cannot be used on a root web; the type conversion would fail. 
            return new Configuration<siteconfigurationsettings></siteconfigurationsettings>(); 
        }   
 
        return Configuration<siteconfigurationsettings></siteconfigurationsettings>.GetConfigurationSettings(web); 
    } 
}
Be Sociable, Share!

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">