Thursday, June 14, 2007

Dynamic.web.config

I don't know about you, but I absolutely hate it when I change something in my web.config and the application restarts. Maybe I just misspelled something in the <appSettings> or changed some custom cache configuration. The app should be able to survive some configuration settings changes without resetting. So I built my own ConfigurationSettings-like class and named it DynamicConfigurationSettings. It reads a web.config-like file (which I call dynamic.web.config, but could be anything) and whenever the file changes, its values are automatically reloaded (courtesy of FileSystemWatcher and Bjarne Lindberg's FileWaiter). FileWaiter was necessary because FileSystemWatcher raises multiple events when a file is changed, a problem described in this thread. DynamicConfigurationSettings is meant to be used as a drop-in replacement for ConfigurationSettings. It was written for 1.1 but it should be fairly easy to port it to 2.0. Like I said, dynamic.web.config has a schema similar to that of a basic web.config:

<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="configuration">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="configSections">
          <xs:complexType>
            <xs:sequence>
              <xs:element maxOccurs="unbounded" name="section">
                <xs:complexType>
                  <xs:attribute name="name" type="xs:string" use="required" />
                  <xs:attribute name="type" type="xs:string" use="required" />
                </xs:complexType>
              </xs:element>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="appSettings">
          <xs:complexType>
            <xs:sequence>
              <xs:element maxOccurs="unbounded" name="add">
                <xs:complexType>
                  <xs:attribute name="key" type="xs:string" use="required" />
                  <xs:attribute name="value" type="xs:string" use="required" />
                </xs:complexType>
              </xs:element>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

(extracted with Visual Studio). Basically, it supports <appSettings> and <configSections>.
Example:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <configSections>
  <section name="customSection" type="MyApp.CustomSectionHandler, MyAssembly"/>
 </configSections>
  <appSettings>
  <add key="MaxResults" value="300"/>
  </appSettings>
 <customSection>
  <default>hello</default>
  <rules>
   <rule source="something" destination="something else"/>
  </rules>
 </customSection>
</configuration>

To use it, just call DynamicConfigurationSettings.load(@"path\to\dynamic.web.config") in your Application_Start and that's it.

Additionally, whenever it can't find some requested config item, it tries to fetch it from ConfigurationSettings. So, if your code uses DynamicConfigurationSettings, you can override your web.config settings with your dynamic.web.config settings. Example: suppose you have defined MaxResults as 200 in <appSettings> in web.config, and you have the following code:

DynamicConfigurationSettings.load("dynamic.web.config");
int maxResults = Convert.ToInt32(DynamicConfigurationSettings.AppSettings["MaxResults"]);
You'd get 300, as it's defined in dynamic.web.config. BUT, if we remove the definition from dynamic.web.config, you get 200, as defined in web.config.
Before going to the code, a piece of advice/disclaimer:

DON'T use this to store critical data! I only use it to store cache sizes and other non-critical stuff. I tried hard to make it as thread-safe and fault-tolerant as possible, but be aware that it's very dangerous to change configuration at runtime. If you edit you dynamic.web.config and save it while it's not well-formed, and your application crashes, it's not my fault (it shouldn't happen, but still).
I recommend that you wrap every access to a strongly-typed value in DynamicConfigurationSettings (like the maxResults example above) with try..catch and provide sensible defaults, so you won't crash everything if you write something like <add key="MaxResults" value="Eoo"/>

You have been warned. Now here's the code, go have fun :-)
As usual, comments and bugfixes are very welcome.

FileWaiter.cs
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;

namespace Utils
{
 /// <summary>
 /// Waits for a file to close
 /// From Bjarne Lindberg (http://www.thescripts.com/forum/post1453548-8.html)
 /// </summary>
 public class FileWaiter
 {
  public string fileName;

  public FileWaiter(string fileName) {
   this.fileName = fileName;
  }

  public void DoWait(int timeout) {
   DoWait(new TimeSpan(0, 0, 0, 0, timeout));
  }

  public void DoWait() {
   DoWait(TimeSpan.MinValue);
  }

  public void DoWait(TimeSpan timeout) {
   Debug.Assert(fileName != null);
   Thread t = new Thread(new ThreadStart(ThreadWait));
   t.Start();
   if (timeout > TimeSpan.MinValue)
    t.Join(timeout);
   else
    t.Join();
  }

  private void ThreadWait() {
   do {
    //Start waiting..
    Thread.Sleep(500);
   } while (File.Exists(fileName) && isFileOpen(fileName));
  }

  protected static bool isFileOpen(string fileName) {
   FileStream s = null;
   try {
    s = File.Open(fileName, FileMode.Open,
                  FileAccess.ReadWrite,
                  FileShare.None);
    return false;
   }
   catch (IOException) {
    return true;
   }
   finally {
    if (s != null)
     s.Close();
   }
  }
 }
}

DynamicConfigurationSettings.cs
using System;
using System.Collections;
using System.Configuration;
using System.IO;
using System.Xml;
using System.Collections.Specialized;
using log4net;

namespace Utils
{
 /// <summary>
 /// Similar to <see cref="System.Configuration.ConfigurationSettings"/>, but accepts changes in configuration file at runtime without restarting the application
 /// </summary>
 public class DynamicConfigurationSettings
 {
  private static readonly ILog log = LogManager.GetLogger(typeof(DynamicConfigurationSettings));
  
  /// <summary>
  /// Gets configuration settings in the configuration section.
  /// </summary>
  public static NameValueCollection AppSettings {
   get {
    lock (padLock) {
     if (_appSettings != null)
      return _appSettings;
     return ConfigurationSettings.AppSettings;     
    }
   }
  }
  
  private static Hashtable configSectionsCache = new Hashtable();

  /// <summary>
  /// Returns configuration settings for a user-defined configuration section.
  /// </summary>
  /// <param name="sectionName">The configuration section to read.</param>
  /// <returns>The configuration settings for sectionName.</returns>
  public static object GetConfig(string sectionName) {
   if (configSectionsCache[sectionName] == null)
    try {
     SectionConfig config = (SectionConfig) sections[sectionName];
     IConfigurationSectionHandler sectionHandler = (IConfigurationSectionHandler) Activator.CreateInstance(Type.GetType(config.handlerName));
     configSectionsCache[sectionName] = sectionHandler.Create(null, null, config.node);
    } catch (Exception) {
     configSectionsCache[sectionName] = ConfigurationSettings.GetConfig(sectionName);
    }
   return configSectionsCache[sectionName];
  }

  /// <summary>
  /// Loads configuration from xml document
  /// </summary>
  /// <param name="config">Document to load</param>
  public static void load(XmlDocument config) {
   XmlNodeList configSections = config.SelectNodes("/configuration/configSections/section");
   
   lock (padLock) {
    sections = new Hashtable();
    
    foreach (XmlNode section in configSections) {
     SectionConfig sc = new SectionConfig();
     string sectionName = section.Attributes["name"].InnerText;
     sc.node = config.SelectSingleNode("/configuration/" + sectionName);
     sc.handlerName = section.Attributes["type"].InnerText;
     sections[sectionName] = sc;
    }
   
    XmlNode nodeAppSettings = config.SelectSingleNode("/configuration/appSettings");
    NameValueSectionHandler nvsh = new NameValueSectionHandler();   
    NameValueCollection nvc = (NameValueCollection) nvsh.Create(null, null, nodeAppSettings);
    _appSettings = new NameValueCollection();
    _appSettings.Add(ConfigurationSettings.AppSettings);   
    foreach (string k in nvc.Keys)
     _appSettings[k] = nvc[k];    
    configSectionsCache = new Hashtable();
   }

  }
  
  /// <summary>
  /// Loads configuration from xml file and reloads when it changes
  /// </summary>
  /// <param name="filename"></param>
  public static void load(string filename) {
   _filename = filename;
   XmlDocument config = new XmlDocument();
   try {
    config.Load(filename);
    load(config);
    if (fsw != null) {
     fsw.Dispose();
    }
    fsw = new FileSystemWatcher(Path.GetDirectoryName(filename));
    fsw.Changed += new FileSystemEventHandler(fsw_Changed);
    fsw.EnableRaisingEvents = true;       
   } catch (Exception e) {
    log.ErrorFormat("Error loading dynamic.web.config: {0}\n{1}", filename, e.ToString());
   }
  }
  
  /// <summary>
  /// Loads configuration from xml stream
  /// </summary>
  /// <param name="s"></param>
  public static void load(Stream s) {
   XmlDocument config = new XmlDocument();
   StreamReader sr = new StreamReader(s);
   try {
    config.LoadXml(sr.ReadToEnd());
    load(config);
   } catch (Exception e) {
    log.Error("Error loading dynamic.web.config:", e);
   } finally {
    sr.Close();
   }
  }
  
  /// <summary>
  /// Loads configuration from xml string
  /// </summary>
  /// <param name="s"></param>
  public static void loadString(string s) {
   XmlDocument config = new XmlDocument();
   try {
    config.LoadXml(s);
    load(config);          
   } catch (Exception e) {
    log.Error("Error loading dynamic.web.config:", e);
   }
  }

  /// <summary>
  /// Handles changes in config file
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="e"></param>
  private static void fsw_Changed(object sender, FileSystemEventArgs e) {
   if (e.Name.ToLower() == Path.GetFileName(_filename.ToLower()) && e.ChangeType == WatcherChangeTypes.Changed) {
    FileWaiter fw = new FileWaiter(e.FullPath);
    fw.DoWait();
    load(e.FullPath);
   }
  }

  private static NameValueCollection _appSettings;  
  private static FileSystemWatcher fsw;
  private static string _filename;
  private static Hashtable sections;
  private static object padLock = new object();
  
  private class SectionConfig {
   public string handlerName;
   public XmlNode node;
  }

 }
}

Sunday, June 10, 2007

Exception Translation with Castle Windsor

Warning: the following code has become obsolete since the release of Castle RC3. I'll update as soon as I can. The concepts still apply, though.

Recently I've been writing an app using Castle Windsor. While I was writing the service classes, the following problem came up: I don't want my services' consumers catching Dao exceptions. I'm using ActiveRecord to persist domain objects, but it could have been directly NHibernate, or iBatis. Either way, whoever uses the service classes should be ignorant about persistance details. Suppose the service interface looks like this:

public interface ISomeService {
 void ProvideService();
}
and its implementation lets an internal exception pass, which we could model like this to make things simple:
public class SomeService : ISomeService {
 public void ProvideService() { 
   throw new InternalException();
  }
}

public class InternalException : ApplicationException {}
Now we'd like to translate InternalException into an ExternalException, which is a service-level exception. We could manually catch InternalException in every method of SomeService, but that's annoying.
Let's use Windsor instead. We need three components: the exception translator (which says what external exception corresponds to each internal exception), the interceptor (which actually does the try..catch) and a contributor (which installs the interceptor and translator).
First, let's write a test that should pass when we finish installing all three components:
[TestFixture]
public class ExceptionTranslatorTests {
  [Test]
  [ExpectedException(typeof (ExternalException), "1")]
  public void ExceptionIsTranslated() {
    IWindsorContainer container = new WindsorContainer();
    container.AddComponent("service", typeof (ISomeService), typeof (SomeService));
    ISomeService svc = container.Resolve<ISomeService>();
    svc.ProvideService();
  }
}

public class ExternalException : ApplicationException {
  public ExternalException(string message) : base(message) {}
  public ExternalException() {}
  public ExternalException(string message, Exception innerException) : base(message, innerException) {}
}
If we run this test now, it will fail, since the exception thrown by ProvideService() is still InternalException instead of the expected ExternalException.
Now let's build the translator:
public interface IExceptionTranslator {
 Exception translate(Exception e);
}

public class SomeExceptionTranslator : IExceptionTranslator {
  public Exception translate(Exception e) {
    return new ExternalException("1", e);
  }
}
Simple, no? Just make sure you wrap the original exception, so you don't lose stack information and stuff.
Now for the generic interceptor:
public class ExceptionTranslationInterceptor<T> : IMethodInterceptor where T : IExceptionTranslator {
  private T translator;

  public ExceptionTranslationInterceptor(T translator) {
   this.translator = translator;
  }

  public object Intercept(IMethodInvocation invocation, params object[] args) {
    try {
      return invocation.Proceed(args);
    } catch (Exception e) {
      throw translator.translate(e);
    }
  }
}
Notice that the actual translation logic will be injected by Windsor into the interceptor. Oh, and make sure theIMethodInterceptor you implement is the one in Castle.Core.Interceptor, not the one in AopAlliance.Intercept.
You may wonder why the interceptor takes <T>, you'll see why in a second when we build the contributor:
public class ExceptionTranslatorContributor<T, T2> : IContributeComponentModelConstruction
 where T2 : IExceptionTranslator {
  public void ProcessModel(IKernel kernel, ComponentModel model) {
    if (typeof (T).IsAssignableFrom(model.Implementation)) {
      kernel.AddComponent(typeof (T2).ToString(), typeof (T2));
      kernel.AddComponent(string.Format("ExceptionTranslationInterceptor<{0}>", typeof(T2)), typeof(ExceptionTranslationInterceptor<T2>));
      model.Interceptors.Add(new InterceptorReference(typeof (ExceptionTranslationInterceptor<T2>)));
    }
  }
}

This contributor takes two types: T, which is the service to intercept, and T2, which is the IExceptionTranslator. It installs the translator and the corresponding interceptor as components, then it installs the interceptor as such. By making the interceptor and the contributor generic, we can easily associate different translators for different services, just by setting up the contributor in the container, like this:
[TestFixture]
public class ExceptionTranslatorTests {
  [Test]
  [ExpectedException(typeof (ExternalException), "1")]
  public void ExceptionIsTranslated() {
    IWindsorContainer container = new WindsorContainer();
    container.Kernel.ComponentModelBuilder.AddContributor(new ExceptionTranslatorContributor<SomeService, SomeExceptionTranslator>());
    container.AddComponent("service", typeof (ISomeService), typeof (SomeService));
    ISomeService svc = container.Resolve<ISomeService>();
    svc.ProvideService();
  }
}
And now the test passes!
Full code shown here is available here.