The Very Last Configuration Section Handler I'll Ever Need

aspnet config 0 comments suggest edit

UPDATE: In ASP.NET 2.0, there’s an even easier approach that supercedes this one. I wrote about it here.

A while back Craig Andera wrote an excellent article entitled The Last Configuration Section Handler I’ll Ever Need which outlines a really nice and flexible configuration section handler. It is certainly a big time saver and was the leading contender as the last one I would ever need as well. That is until I realized it lacked one minor feature I need: the ability to reload itself when the config file changes.

Before continuing, please do go and read his article for background. It’s pretty short and well written. No really, go read it while I listen to

American Dream (Joey Negro Club Mix) - Jakatta - Essential Mix - Mixed By Pete Tong (5:22)

Having read his article, it should be apparent why reloading the settings when the file changes is a challenge. Mainly because you most likely will have a reference to your simple settings object (such as the MyStuff class in the article) which has to somehow be notified that a change has occurred. As you can see from the sample below, classes used to hold settings (such as the MyStuff class) are deliberately kept very simple in order to facilitate writing many of them. There’s no facility to watch for changes to the config file.

public class MyStuff {
  public float Foo {
    get;
    set;
  }

  public string Bar {
    get;
    set;
   }
}

So after a bit of thought and a bit more beer, I came up with a solution that I believe still retains the simplicity of his approach, while adding the ability to have the settings dynamically get updated when the config file changes. My approach is also backwards compatible with existing setting objects created using his approach. Existing setting objects will not need any changes to work, though they won’t get the benefit of this new feature.

I’ve extended Craig’s solution with the addition of the XmlSectionSettingsBase abstract class. The purpose of this class is to act as the base class for your simple setting object. To add the ability to reload itself, just have your class inherit from XmlSectionSettingsBase and call the UpdateChanges() method before each property getter. As an example, I’ve modified the above settings object:

public class MyStuff : XmlSectionSettingsBase {
  private float foo;
  private string bar;

  public float Foo {
    get {
      UpdateChanges();
      return foo;
    }
    set { foo = value ; }
  }

  public string Bar {
    get {
      UpdateChanges();
      return bar;
    }
    set { bar = value;
  }
}

That’s it! That is the total amount of changes to the settings class needed so that it will now track changes. I’ve also updated the XmlSerializerSectionHandler class as well. It’s now pretty small:

public class XmlSerializerSectionHandler : IConfigurationSectionHandler {
  public object Create(object parent, object context, XmlNode section)  {
    return XmlSectionSettingsBase.LoadSettings(section);
  }
}

Which leads us to the XmlSectionSettingsBase class which is the workhorse of my scheme. You’ll notice that I’ve removed all the logic from the section handler for loading the settings object from the config file. The reason for this is that the instance of the settings object has to know how to load itself from the config file if its going to watch it for changes. Since I didn’t want to duplicate logic, I moved that code to this class. I’ll outline the class with some broad strokes and let you download the source code for the complete picture.

First, let’s start with the static LoadSettings method.

public static object LoadSettings(XmlNode section) {
  object settings = DeserializeSection(section);
  XmlSectionSettingsBase xmlSettings = settings as XmlSectionSettingsBase;
  if(xmlSettings != null) {
    xmlSettings._rootName = section.Name;
    ((XmlSectionSettingsBase)settings).WatchForConfigChanges();
  }
  return settings;
}

This method deserializes an instance of your settings object and checks to see if it inherits from XmlSectionSettingsBase. If not, it just returns, keeping this approach backwards compatible. Otherwise it calls WatchForConfigChanges which sets up a FileSystemWatcher to monitor for changes to the config file. Also notice that it stores the name of the config section node in a private member (_rootName) of the settings class. This becomes important later when we need to reload the settings.

void WatchForConfigChanges() {
  FileInfo configFile = new FileInfo(
    AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
  try {
    _watcher = new FileSystemWatcher(configFile.DirectoryName);
    _watcher.Filter = configFile.Name;
    _watcher.NotifyFilter = NotifyFilters.LastWrite;
    _watcher.Changed += new FileSystemEventHandler(OnConfigChanged);
    _watcher.EnableRaisingEvents = true;
  }
  catch(Exception ex) {
    Log.Error("Configuration problem.", ex);
    throw new ConfigurationException("An error occurred while attempting to watch for file system changes.", ex);
  }
}

When a change occurs, the OnConfigChanged method is called. This method simply updates a boolean flag. This is the flag that the UpdateChanges method mentioned earlier checks before actually applying changes to the settings object.

void OnConfigChanged(object sender, FileSystemEventArgs e) {
  _isDataValid = false;
}

protected void UpdateChanges() {
  if(!_isDataValid)
    ReloadSettings();
}

Now we hit upon the ReloadSettings method. This method uses the name of the node containing the config section we stored earlier (_rootName) in order to load an XmlDocument containing the new settings from the config file.

void ReloadSettings() {
  XmlDocument doc = new XmlDocument();
  doc.Load(AppDomain.CurrentDomain.SetupInformation.ConfigurationFile);
  XmlNodeList nodes = doc.GetElementsByTagName(_rootName);
  if(nodes.Count > 0) {
    //Note: newSettings should not watch for config changes.
    XmlSectionSettingsBase newSettings =
      DeserializeSection(nodes[0]) as XmlSectionSettingsBase;
    newSettings._isDataValid = true;
    CopySettings(newSettings);
  }
  else
    throw new System.Configuration.ConfigurationException("Configuration section " + _rootName + " not found.");
}

The path to the config file is retrieved via a the AppDomain.CurrentDomain.SetupInformation.ConfigurationFile property. Once we have the section, we can call DeserializeSection to get an instance of our settings class with the new settings. This method is pretty is the same as the Create method in Craig’s version.

static object DeserializeSection(XmlNode section) {
  XPathNavigator navigator = section.CreateNavigator();
  string typename = (string)navigator.Evaluate("string(@type)");
  Type type = Type.GetType(typename);
  if(type == null)
    throw new ConfigurationException("The type ’" + typename + "’ is not a valid type. Double check the type parameter.");
  XmlSerializer serializer = new XmlSerializer(type);
  return serializer.Deserialize(new XmlNodeReader(section));
}

After loading this new settings instance, I overwrite the current instance’s property values with the values from the new settings instance via a call to CopySettings.

void CopySettings(object newSettings) {
  if(newSettings.GetType() != this.GetType())
    return;
  PropertyInfo[] properties = newSettings.GetType().GetProperties();
  foreach(PropertyInfo property in properties) {
    if(property.CanWrite && property.CanRead) {
      property.SetValue(this, property.GetValue(newSettings, null), null);
    }
  }
}

CopySettings uses reflection to iterate through all the public get properties of the new settings object and sets the corresponding property of the current instance if the current property can be written to.

Unfortunately this post is very old and I don’t have the source code for it anymore. In any case, there are better approaches you can use with ASP.NET 2.0 and above.

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

12 responses

  1. Avatar for Phil's Boss
    Phil's Boss June 25th, 2004

    This isn't what we are paying you for - now get back to work. Also, the legal department wants to remind you that all of your work product on company times belongs to the company - even this Blog.

  2. Avatar for Haacked
    Haacked June 25th, 2004

    Well I develop most of this in my free time and post it while at work. ;)

  3. Avatar for Craig
    Craig June 25th, 2004

    So I should I rename mine "The Next-to-Last Configuration Section Handler I'll Ever Need" ? :)



    You should check out the Configuration Management Application Block from Microsoft. It does pretty much the same thing you've got here, and a lot more. Of course, it's also correspondingly heavier, but it does have the advantage of not requiring the UpdateChanges call in every property getter.

  4. Avatar for Koba
    Koba June 25th, 2004

    Legal departments suck. If you have any problems with them, the best way to deal with them is to either bribe or blackmail them.

  5. Avatar for Haacked
    Haacked June 25th, 2004

    Ha ha ha.. only if you start using mine. I have looked into the Configuration Management Application Block, but I liked yours better because of the sheer minimalism (especially compared to the block).



    But now that I have a better idea about what goes on underneath the hood, it's probably time to take another look. What do you use yourself?

  6. Avatar for Craig
    Craig July 8th, 2004

    I tend to just use XmlSerialization straight up against a different file. It means I can use the same approach for user settings (in Documents and Settings, where users actually have write permission) and for application settings (in Program Files).

  7. Avatar for Steven James
    Steven James February 9th, 2005

    Check out the port of the Spring framework for .NET. It totally removes the need to write configuration handlers at all.









  8. Avatar for Ashish
    Ashish June 10th, 2005

    You guys made me curious. So when the new values from the config file are loaded, what happens to the in-memory cached state?



    I know if you just restart the .net application, that state is lost. Is there a way to retain the values?



    Let me know.

  9. Avatar for Craig
    Craig April 25th, 2006

    BTW, the link to the original article is broken. The correct link is
    http://www.pluralsight.com/...

  10. Avatar for Haacked
    Haacked April 25th, 2006

    Thanks! I fixed it.

  11. Avatar for Pramod Rane
    Pramod Rane January 24th, 2007

    Very Nice.

  12. Avatar for Joel Holder
    Joel Holder July 22nd, 2010

    Hi Phil, link to download the source is broken. Do you still have this thing laying around. Would love to get my hands on it to solve the particular problem that it addresses. Thanks..
    Joel