Using the Applications Setting Manager with Web App Config (Developing Applications for SharePoint 2010)

We got a recent post to our discussion forums about managing settings for a web application from a web part.  I thought this would be a good opportunity to go into some more detail about some of the special handling required for Farm and web application settings, with some example code. 

Explanation 

Web application and Farm settings can't be written from a content web application.  A security exception will be thrown when writing to either of these locations a content web site.  A content web site is essentially sites that end users access, either internally or externally, and will typically be most of the web apps in your farm.  This security feature ensures that settings at these levels can't be compromised due to a security flaw on a page being displayed to end users.  Settings at these levels can be read without any issues. 

The SharePoint Guidance Library included in the Developing Applications for SharePoint 2010 includes an Application Settings Manager that can read and write settings at different levels, including the farm and the web application.  The application settings manager classes help manage settings, and provide a feature for hierarchical lookup of settings (web->site->web application->farm).  At the web application and farm levels the application settings manager uses a class derived from SPPersistedObject that is a child of the farm or web application.  For more info on the design you can see the Application Settings Manager detailed documentation. 

Since these classes are a child of the web application or farm, the same security rules apply to them as apply to the parent object.  Therefore you can't write the settings to  the farm level or the web app level from a web part hosted in a content web app.  I wrote a quick spike to demonstrate. 

Here is a simple web part:

 

The code that is behind both of these buttons is as follows: 

 protected void OnWriteWebAppSettings(object sender, EventArgs e)

{

    var locator = SharePointServiceLocator.GetCurrent();

    var configMgr = locator.GetInstance<IConfigManager>();



    var webAppPropBag = configMgr.GetPropertyBag(ConfigLevel.CurrentSPWebApplication);

    var setting = 1;

    configMgr.SetInPropertyBag(settingName, setting, webAppPropBag);

    LiteralResults.Text = "saved setting: " + settingName + " Value: '" + setting + "'";

}



protected void OnReadWebAppSettings(object sender, EventArgs e)

{

    ReadWebAppSettings();

}



protected void ReadWebAppSettings()

{

    var locator = SharePointServiceLocator.GetCurrent();

    var configMgr = locator.GetInstance<IConfigManager>();



    var webAppPropBag = configMgr.GetPropertyBag(ConfigLevel.CurrentSPWebApplication);

    var setting = 1;

    setting = configMgr.GetFromPropertyBag<int>(settingName, webAppPropBag);

    LiteralResults.Text = "retrieved setting: " + settingName + " Value: '" + setting + "'";

}

When the read button is pressed, then a value of 0 is returned (in this case the value has not been defined, and 0 is the default value for an integer, which is returned since the setting is not defined.  For a complex object, a null would be returned instead).  When the write button is pressed, then a security exception is thrown:

 System.Security.SecurityException was caught

  Message=Access denied.

  Source=Microsoft.SharePoint

  StackTrace:

       at Microsoft.SharePoint.Administration.SPPersistedObject.BaseUpdate()

       at Microsoft.Practices.SharePoint.Common.Configuration.SPWebAppPropertyBag.set_Item(String key, String value)

       at Microsoft.Practices.SharePoint.Common.Configuration.ConfigManager.SetInPropertyBag(String key, Object value, IPropertyBag propertyBag)

  InnerException: 

You can see that when the update is attempted, a security exception gets thrown.  This exception will be re-thrown by the ConfigManager as a ConfigurationException. 

 Option 1: Add/Update the values in a feature receiver

One way to address this is to update or set any values in a feature receiver.  If you deploy this as a site or web scoped feature, then there is some special handling involved.  A site or web scoped feature is activated from a content web site.  Therefore if you put the logic into the FeatureActivated event, you will again get a security exception.  You can put this logic instead into the FeatureInstalled event.  FeatureInstalled gets fired when the feature is installed, which typically occurs through a higher privileged account like the command line, or a timer service.  However FeatureInstalled has another issue you'll need to contend with.  There is no site or web context passed into the FeatureInstalled event (since at installation it's not being associated to a web or site - that happens when the feature is activated).  As a result you will need to generate the site context yourself as in the following example:

 public override void FeatureInstalled(SPFeatureReceiverProperties properties)

{

    var locator = SharePointServiceLocator.GetCurrent();

    var configMgr = locator.GetInstance<IConfigManager>();



    using (SPSite site = new SPSite("https://localhost/sites/test"))

    {

        configMgr.SetWeb(site.RootWeb);

        var webAppPropBag = configMgr.GetPropertyBag(ConfigLevel.CurrentSPWebApplication);

        var setting = 1;

        configMgr.SetInPropertyBag(settingName, setting, webAppPropBag);

    }

}

In general though its best to deploy a setting at the same level it is stored if you are deploying with a feature.  This way you can be assured that the feature is only installed/activated once at the appropriate scope, and don't need to worry about conflicts or reference counting (if for example you figured out a way to set the web application setting in a site FeatureActivated event, then how do you decide when to remove it since the feature may have been activated muliple times within the web application?).  If you deploy this instead as a web application feature, then you can write and remove the setting in the FeatureActivated/FeatureDeactivating code since a web app feature can't be activated from a content web app.  The below example shows a FeatureActivated and FeatureDeactivating code for a web application scoped feature for adding settings to the web application.  Remember that web application scoped features are automatically activated and deactivated by default when a feature is installed/uninstalled.  This behavior is controlled by the attribute ActivateOnDefault on the Feature element.

 public override void FeatureActivated(SPFeatureReceiverProperties properties)

{

    var locator = SharePointServiceLocator.GetCurrent();

    var configMgr = locator.GetInstance<IConfigManager>();

    SPWebApplication webapp = properties.Feature.Parent as SPWebApplication;

    IPropertyBag bag = new SPWebAppPropertyBag(webapp);



    configMgr.SetInPropertyBag(settingName, 2, bag);

}







public override void FeatureDeactivating(SPFeatureReceiverProperties properties)

{

    var locator = SharePointServiceLocator.GetCurrent();

    var configMgr = locator.GetInstance<IConfigManager>();

    SPWebApplication webapp = properties.Feature.Parent as SPWebApplication; 

    IPropertyBag bag = new SPWebAppPropertyBag(webapp);



    configMgr.RemoveKeyFromPropertyBag(settingName, bag);

}

 Option 2: Add a configuration page to central admin

 Central admin runs in a different application pool from the content web apps.  From central admin you have permissions to change any farm or web app level setting.  A how-to provide in the guidence describes the task of adding a custom page to central admin called How to: Deploy an Application Page to Central Administration.  In my next post I will walk through a simple example page for editing web application settings in central admin.