Ayende @ Rahien

Refunds available at head office

Envrionment Validation and Windsor Extensibility

So I was in Jermey Miller's talk about maintainable software echo system, and one of the thing that he mentioned that StructureMap does really well is the ability to ask the container to perform envrionment validations, to make sure that the envrionment is ready for us.

I really liked the idea, so I pulled up the laptop and started spiking how to handle this issue. First, let us see Jeremy's solution:

public class FileTextReader : ITextReader
{
	[ValidateConfiguration]
	public void ValidateFileExistance()
	{
		if (File.Exists(fileName) == false)
			throw new FileNotFoundException("Could not find file " + fileName);
	}
}

So, when you ask structure map to validate the environment, it will run all the methods that have been decorated with [ValidateConfiguration].

So, how do that that in Windsor?

The most important thing to realize with Windsor is that it is a container that was built to be extensible. Something like that is not going to be a change to the container, it will be an extension, not a change to the container itself. Extensions are usually facilities, like this one:

public class ValidationFacility : AbstractFacility
{
	private readonly List<string> componentsToValidate = new List<string>();

	protected override void Init()
	{
		Kernel.AddComponent<ValidateConfiguration>();
		IHandler handler = Kernel.GetHandler(typeof(ValidateConfiguration));
		handler.AddCustomDependencyValue("componentsToValidate",
			 componentsToValidate
			);
		Kernel.ComponentRegistered += OnComponentRegistered;
	}

	public void OnComponentRegistered(string key, IHandler handler)
	{
		foreach (MethodInfo method in 
handler.ComponentModel.Implementation.GetMethods()) { bool isValidateMethod = method
.GetCustomAttributes(typeof(ValidateConfigurationAttribute), true)
.Length != 0; if (isValidateMethod) { componentsToValidate.Add(key); break; } } } }

This extends the container, and whenever a component is registered, I am checking if I need to add that to the list of components that needs validation. I am doing a  tiny bit of cheating here and passing the componentsToValidate as a reference to the component, it is simpler that way, but the component gets the same instance, which is probably not what I would like to do with it for other approaches. I would usually got with a sub resolver that matched that issue, if I was doing something like this for more interesting purposes.

Anyway, here how the ValidationConfiguration class is built:

public class ValidateConfiguration
{
	private readonly ICollection<string> componentsToValidate;
	private readonly ILogger logger;
	private readonly IKernel kernel;

	public ValidateConfiguration(
		ICollection<string> componentsToValidate,
		ILogger logger,
		IKernel kernel)
	{
		this.componentsToValidate = componentsToValidate;
		this.logger = logger;
		this.kernel = kernel;
	}

	public void PerformValidation()
	{
		foreach (string key in componentsToValidate)
		{
			ValidateComponent(key);
		}
	}

	private void ValidateComponent(string key)
	{
		IHandler handler = kernel.GetHandler(key);
		if (handler == null)
		{
			logger.Warn("Component {0} was removed before it could be validated", key);
			return;
		}
		try
		{
			object component = handler.Resolve(CreationContext.Empty);
			foreach (MethodInfo method in component.GetType().GetMethods())
			{
				bool isValidateMethod = method.GetCustomAttributes(typeof(ValidateConfiguration), true).Length == 0;
				if (isValidateMethod)
				{
					ExecuteValidationMethod(component, method);
				}
			}
		}
		catch (TargetInvocationException e)
		{
			logger.Error("Failed to run validation for {0}, because: {1}", key, e.InnerException);
		}
		catch (Exception e)
		{
			logger.Error("Failed to run validation for {0}, because: {1}", key, e);
		}
	}

	private void ExecuteValidationMethod(object component, MethodBase method)
	{
		try
		{
			method.Invoke(component, new object[0]);
		}
		catch (Exception e)
		{
			logger.Error("Failed to validate {0}.{1}. Error: {2}",
				method.DeclaringType,
				method.Name,
				e);
		}
	}
}

This is a class that has some deep association with the container. It is usually not something that I would like in my application services, but it is fine for instrastracture pieces, like this one.

Now that I have that , I can actually test the implementation:

IWindsorContainer container = new WindsorContainer();
container.AddFacility("validation", new ValidationFacility());
container.AddComponent<ITextReader, FileTextReader>();
container.Kernel.GetHandler(typeof(ITextReader))
	.AddCustomDependencyValue("fileName", "foo");
container.AddComponent<ILogger, ConsoleLogger>();

ValidateConfiguration resolve = container.Resolve<ValidateConfiguration>();
resolve.PerformValidation();

And this will go over everything and perform whatever validations needs to be done.

As I said, I really like the idea, and extending this to a build task is really trivial (especially if you are using Boo Build System to do things).

The main point, however, is that I managed to write this piece of code (around 100 lines or so), during Jeremy's talk, so from the time he talked about that feature to the time that he finished, I already got that done. This actually has nothing to do with my personal prowess with code, but it has a lot to do with the way Windsor it built, as a set of services that can be so readily extended.

After I have gotten used to the style that Windsor has, it is getting really addictively easy to start extending the container in interesting ways. I highly recommend that you will take a look at those features, they are interesting both from "what I can do with them" and from "what design allowed this".

Comments

Michael
11/29/2007 01:24 PM by
Michael

That is a neat little thing. Not sure I can use it at the moment, but always nice to have a tool in the box when you need it!

Torkel
11/29/2007 04:36 PM by
Torkel

The extensibility of Windsor is really great. I extended it to allow for dynamic component selection (based on current user type) with very few lines of code and it removed the need for many otherwise necessary factories.

hammett
11/29/2007 05:31 PM by
hammett

Cool ! Btw, I think you can reduce a little more your code by using

method.IsDefined(typeof(ValidateConfigurationAttribute))

instead of GetCustomAttributes(..).Length != 0

Just me nitpicking, as usual....

Gabriel
11/29/2007 10:11 PM by
Gabriel

I started to write facilities soon after starting to use the Windsor container in my projects. It's really easy to do and has provided me with some real benefits!

Comments have been closed on this topic.