Castle Windsor version 2.0 has just been released (despite the fact that version 1.0 has never existed). The biggest new feature in this release is the fluent configuration interface which lets you set up your components in code in an elegant way (as opposed to configuring in xml).
A component which in xml is set up like this:
<component
id="service"
lifestyle="transient"
service="Some.Namespace.IService, MyAssembly"
type="Some.Namespace.Service, MyAssembly">
<parameters>
<lang>Norwegian</lang>
</parameters>
</component>
can now be configured fluently like this:
var container = new WindsorContainer();
container.Register(
Component
.For<IService>()
.ImplementedBy<Service>()
.LifeStyle.Transient
.Parameters(Parameter.ForKey("lang").Eq("Norwegian")));
For more examples of how the fluent interface works, read this.
I am really enjoying the experience of using this new fluent interface; it is much easier to configure a component first time, and you get the full support of your compiler and from ReSharper. At last I am able to rename classes (through the refactoring functionality in ReSharper) without having to hunt down and fix the configuration for the component in the xml file.
Configuring parameters
Notice the parameter lang in the example above; the value Norwegian is hardwired directly in as a parameter (is presumably injected into the component's constructor). In the real world you would probably want the keep track of all such properties separately in the properties node, thus promoting reuse and easing maintenance. Your xml configuration might look like this:
<properties>
<language>Norwegian</language>
</properties>
<components>
<component
id="service"
lifestyle="transient"
service="Some.Namespace.IService, MyAssembly"
type="Some.Namespace.Service, MyAssembly">
<parameters>
<lang>#{language}</lang>
</parameters>
</component>
</components>
There is a tension between the benefits and drawbacks of having the configuration in code as opposed to in separate xml files: in code it is easier to manage the configuration while under development, while xml-based configuration supports easy changes to a system that has already been deployed. I feel that the smartest path would be to configure the components in code, but keep the properties defined in xml.
I am currently retrofitting fluent castle configuration on a relatively large application that is totally castle.windsor based. My initial gut feeling was that this new version of castle as a matter of course supported this "smartest path". Unfortunately, it doesn't, and so I was left to my own devices: enter ConfiguredParameter.
ConfiguredParameter
With the ConfiguredParameter functionality in place, I am able to configure my component as follows:
Properties in xml:
<castle>
<properties>
<language>Norwegian</language>
</properties>
</castle>
Component in code:
var container = new WindsorContainer();
container.Register(
Component
.For<IService>()
.ImplementedBy<Service>()
.LifeStyle.Transient
.Parameters(ConfiguredParameter.ForKey("lang").GetValue("language")));
To make it clear, the line ConfiguredParameter.ForKey("lang").GetValue("language") will look up the value for the configured property language, and this value will be injected into the component at lang (which presumably is a parameter in the constructor of the type Service).
The functionality must be bootstrapped as your application starts (before your container is initialized), and this will typically be accomplished like this:
InitializeConfiguredParameters.Initialize();
Here the application configuration file will be parsed, and any additional castle configuration files (included through the use of the <include> element of castle) will be included. There is a single overload to the Initialize() method where you can explicitly indicate which config file (.config or .xml) to parse, but this is mostly useful in testing scenarios.
This functionality is rather simple, and the implementation consists of just two types in addition to a couple of parser classes responsible for the actual parsing of the configuration files. These two types are ConfiguredParameter and InitializeConfiguredParameters. Below you will find the definition of the two types.
On the Road Map
The ability to use configured key value pairs from the appSettings element of the application configuration file might be nice, and I'll implement it whenever I need it.
The Code
using System;
using System.Collections.Generic;
using System.Configuration;
using Castle.MicroKernel.Registration;
/// <summary>
/// Used to access parameters that are configured within a standard
/// castle.windsor properties element
/// </summary>
public class ConfiguredParameter
{
#region Static API
private static readonly IDictionary<string, string> configuredParameters = new Dictionary<string, string>();
private static readonly object syncLock = new object();
/// <summary>
/// Adds each parameter in the incoming dictionary to the internal
/// cache of configured parameters
/// </summary>
/// <param name="parameters">The parameters.</param>
internal static void AddParameters(IDictionary<string, string> parameters)
{
// Thread safe!
lock (syncLock)
{
foreach (var pair in parameters)
{
// Skip if already contained, assuming that
// it's some kind of race condition. So, if
// the configuration contains two or more
// identical keys, one of them will "win"
// unpredictably
if (!configuredParameters.ContainsKey(pair.Key))
{
configuredParameters.Add(pair);
}
}
}
}
/// <summary>
/// Resets the ConfiguredParameter infrastructure by clearing all loaded
/// configured parameters. NB! This method should normally not be invoked,
/// and it is defined mostly for testing purposes.
/// </summary>
public static void Reset()
{
configuredParameters.Clear();
}
/// <summary>
/// Sets the name of the parameter on a new instance of
/// ConfiguredParameter and returns it
/// </summary>
/// <param name="parameterKey">The key.</param>
/// <returns></returns>
public static ConfiguredParameter ForKey(string parameterKey)
{
if (configuredParameters.Count == 0)
throw new InvalidOperationException("ConfiguredParameter infrastructure not initialized.");
return new ConfiguredParameter(parameterKey);
}
private static string GetVal(string key)
{
try
{
return configuredParameters[key];
}
catch (KeyNotFoundException e)
{
string message = String.Format("No configured parameter named {0} can be found", key);
throw new ConfigurationErrorsException(message, e);
}
}
#endregion
private readonly string parameterKey;
/// <summary>
/// Initializes a new instance of the <see cref="ConfiguredParameter"/> class.
/// </summary>
/// <param name="parameterKey">The parameter key.</param>
private ConfiguredParameter(string parameterKey)
{
this.parameterKey = parameterKey;
}
/// <summary>
/// Returns a Parameter with the value at the propertyKey in the
/// castle configuration
/// </summary>
/// <param name="propertyKey">The property key.</param>
/// <returns></returns>
public Parameter GetValue(string propertyKey)
{
return Parameter.ForKey(parameterKey).Eq(GetVal(propertyKey));
}
}
using System.IO;
using System.Reflection;
/// <summary>
/// Resposible for initializing the ConfiguredParameter functionality
/// by getting hold of and parsing any relevant configuration files
/// containing castle.windsor parameters
/// </summary>
public static class InitializeConfiguredParameters
{
/// <summary>
/// Initializes this instance by getting hold of the application's
/// configuration file (app.config or web.config) and parsing it
/// looking for configured parameters. If the castle configuration
/// of this file contains include elements, the castle files referenced
/// in these elements are also parsed.
/// </summary>
public static void Initialize()
{
string configFile = Path.GetFileName(Assembly.GetEntryAssembly().Location) + ".config";
if (File.Exists(configFile))
{
InitializeWithFile(configFile);
}
}
/// <summary>
/// Initializes the with file. Valid file types are application files
/// (app.config or web.config) as well as stand alone castle config
/// files
/// </summary>
/// <param name="filename">The filename.</param>
public static void InitializeWithFile(string filename)
{
ReaderBase reader;
if (Path.GetExtension(filename).ToLower() == ".config")
{
reader = new ConfigFileReader(filename);
}
else
{
reader = new PropertiesReader(filename);
}
ConfiguredParameter.AddParameters(reader.GetConfiguredProperties());
}
}