Showing posts with label Inversion of Control. Show all posts
Showing posts with label Inversion of Control. Show all posts

Wednesday, May 13, 2009

Fluent Castle Windsor and Configured Parameters

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());
}
}

Thursday, February 12, 2009

On the Common Service Locator

A couple of years ago I found myself evaluating which IoC container to use in a reusable framework that we where developing at my shop.  A lot of back and forth, I recall, but I ended up choosing Castle.Windsor.

Then I thought: what if I change my mind, or what if a new and hot container hits the streets while my entire code base  is married to Windsor? I decided to implement a static class with the sole purpose of hiding the details of how the actual dependency injection was taking place from the application code (I called it ObjectFactory as a homage to StructureMap, a container which was a strong contender). Later I discovered that I had employed the Gateway Pattern.

The problem with rolling my own gateway, of course, is that I would be forever condemned to writing adapters for each new IoC framework that I would want to support. Additionally, the code base that I was working on at the time could conceivably fall into the hands of devs from other shops (it's a framework). It would be nice to be able to tell them that the code used dependency injection, but that they could choose which ever container they fancied.

Enter the Common Service Locator (released around October 2008, I believe) - CSL for short. This is a light weight framework for solving the exact problem described above. More than anything else it's a standard that the IoC vendors have agreed upon, so that they make the adapters and you don't have to. Presumably, the makers of the containers are going to do a better job than you would here, anyway. Already adapters exist for Windsor, Spring.NET, Unity, StructureMap, Autofac and even for MEF.

I urge you to download the source code. It's a pleasant experience in that you'll understand the entire code base in just a matter of minutes. It immediately becomes clear that the real value on offer here isn't the actual code, it's the fact that this is, and hopefully will remain, an "industry standard" for accessing IoC container functionality on the .net platform.

So, what's the name of the gateway in the CSL framework? It's ServiceLocator.Current, so you resolve instances doing something like this:

var service = ServiceLocator.Current.GetInstance<IMyService>();


ServiceLocator.Current? Quite a few letters, don't you think? According to ctavares (one of the coordinators of this project):



“We were following the precendent set by the .NET framework - Thread.Current, HttpContext.Current, etc.”



Fine by me, but I still don't like it, and I can't see the purpose for me in following this precedent. What you have to realize here, however, is that the thing with CSL isn't the gateway, it's the interface IServiceLocator. This is the standard that the IoC vendors implement. So, it's trivially simple to roll your own gateway that simply gives access to the instance implementing this interface.



Another thing: The standard defined by IServiceLocator exposes only the bare minimum of what a container is. Chances are that your code base is using functionality that is not defined in the interface. In my case, for instance, I need to resolve objects based on its key, i.e. I have the key, but I don't know anything about the type of either of the interface or the concrete type. This functionality is used by an engine that dynamically parses and executes code at runtime.



One more small thing: the only way to register the instance implementing IServiceLocator with ServiceLocator.Current is by feeding it a delegate of type ServiceLocatorProvider which is invoked  to produce the actual locator. Sometimes (in testing scenarios), I already have the locator. So, I expanded my static gateway with an initialization method which takes a locator directly.



I give you: IoC, my static gateway into all that IoC goodness:



/// <summary>
/// This static class implements a gateway to IoC functionality
/// through the Common Service Locator framework
/// </summary>
public static class IoC
{
private static IServiceLocator serviceLocator;
private static ServiceLocatorProvider locatorDelegate;

#region Public API - Initialization

/// <summary>
/// Sets the service locator to use internally. Any previously
/// registered ServiceLocatorProvider registered through the
/// Initialize(ServiceLocatorProvider lctrDelegate) overload
/// will be removed
/// </summary>
/// <param name="locator">The service locator to use internally</param>
public static void Initialize(IServiceLocator locator)
{
serviceLocator = locator;
locatorDelegate = null;
}

/// <summary>
/// Sets the delegate used to get the IServiceLocator used internally. This
/// delegate will be invoked for every call to the GetInstance and
/// GetAllInstances overloads. Any instance set through the
/// Initialize(IServiceLocator locator) overload is nulled, and will be
/// "lost" forever.
/// </summary>
/// <param name="lctrDelegate">The delegate to be used to provide the inner service locator</param>
public static void Initialize(ServiceLocatorProvider lctrDelegate)
{
serviceLocator = null;
locatorDelegate = lctrDelegate;
}

/// <summary>
/// Gets the instance that is registered with the given
/// key.
/// </summary>
/// <param name="key">The key.</param>
/// <returns>The object registered by the given key</returns>
public static object GetByKey(string key)
{
var locator = GetServiceLocator();

if (typeof(IExtendedServiceLocator).IsAssignableFrom(locator.GetType()))
return ((IExtendedServiceLocator) locator).GetByKey(key);

throw new NotSupportedException("Registered locator " + locator.GetType() + " doesnot implement IExtendedServiceLocator");
}

private static IServiceLocator GetServiceLocator()
{
if (serviceLocator != null) return serviceLocator;
if (locatorDelegate == null)
throw new InvalidOperationException(
"IoC must be initialized through Initialize(IServiceLocator locator) or " +
"Initialize(ServiceLocatorProvider lctrDelegate)");
return locatorDelegate.Invoke();
}

#endregion

#region Public API - ServiceLocator

/// <summary>
/// Get an instance of the given <typeparamref name="TService"/>.
/// </summary>
/// <typeparam name="TService">Type of object requested.</typeparam>
/// <exception cref="ActivationException">if there is are errors resolving
/// the service instance.</exception>
/// <returns>The requested service instance.</returns>
public static TService GetInstance<TService>()
{
return GetServiceLocator().GetInstance<TService>();
}

/// <summary>
/// Get an instance of the given <paramref name="serviceType"/>.
/// </summary>
/// <param name="serviceType">Type of object requested.</param>
/// <exception cref="ActivationException">if there is an error resolving
/// the service instance.</exception>
/// <returns>The requested service instance.</returns>
public static object GetInstance(Type serviceType)
{
return GetServiceLocator().GetInstance(serviceType);
}

/// <summary>
/// Get an instance of the given named <typeparamref name="TService"/>.
/// </summary>
/// <typeparam name="TService">Type of object requested.</typeparam>
/// <param name="key">Name the object was registered with.</param>
/// <exception cref="ActivationException">if there is are errors resolving
/// the service instance.</exception>
/// <returns>The requested service instance.</returns>
public static TService GetInstance<TService>(string key)
{
return GetServiceLocator().GetInstance<TService>(key);
}

/// <summary>
/// Get an instance of the given named <paramref name="serviceType"/>.
/// </summary>
/// <param name="serviceType">Type of object requested.</param>
/// <param name="key">Name the object was registered with.</param>
/// <exception cref="ActivationException">if there is an error resolving
/// the service instance.</exception>
/// <returns>The requested service instance.</returns>
public static object GetInstance(Type serviceType, string key)
{
return GetServiceLocator().GetInstance(serviceType, key);
}

/// <summary>
/// Get all instances of the given <typeparamref name="TService"/> currently
/// registered in the container.
/// </summary>
/// <typeparam name="TService">Type of object requested.</typeparam>
/// <exception cref="ActivationException">if there is are errors resolving
/// the service instance.</exception>
/// <returns>A sequence of instances of the requested <typeparamref name="TService"/>.</returns>
public static IEnumerable<TService> GetAllInstances<TService>()
{
return GetServiceLocator().GetAllInstances<TService>();
}

/// <summary>
/// Get all instances of the given <paramref name="serviceType"/> currently
/// registered in the container.
/// </summary>
/// <param name="serviceType">Type of object requested.</param>
/// <exception cref="ActivationException">if there is are errors resolving
/// the service instance.</exception>
/// <returns>A sequence of instances of the requested <paramref name="serviceType"/>.</returns>
public static IEnumerable<object> GetAllInstances(Type serviceType)
{
return GetServiceLocator().GetAllInstances(serviceType);
}

#endregion

}