Castle Windsor strange behaviour wth property injection and factory method
I am using Castle Windsor 2.5.1 in an ASP.NET MVC project and using property injection to create an object which I expect to always be available on a base controller class. I am using a factory to create this object, however if there is an error in the constructor, I do not get a warning from Windsor at all and it just returns my object but without injecting the property.
Is this the expected behaviour, and if so, how can I get an error raised when a factory fails to return anything?
Here is an example
public class MyDependency : IMyDependency
{
public MyDependency(bool error)
{
if (error) throw new Exception("I error on creation");
}
}
public interface IMyDependency
{
}
public class MyConsumer
{
public IMyDependency MyDependency { get; set; }
}
[TestFixture]
public class ProgramTest
{
[Test]
public void CreateWithoutError() //Works as expected
{
var container = new WindsorContainer().Register(
Component.For<IMyDependency>().UsingFactoryMethod(() => new MyDependency(false)).LifeStyle.Transient,
Component.For<MyConsumer>().LifeStyle.Transient
);
var consumer = container.Resolve<MyConsumer>();
Assert.IsNotNull(consumer);
Assert.IsNotNull(consumer.MyDependency);
}
[Test]
public void CreateWithError_WhatShouldHappen() //I would expect an error since it can't create MyDependency
{
var container = new WindsorContainer().Register(
Component.For<I开发者_开发百科MyDependency>().UsingFactoryMethod(() => new MyDependency(true)).LifeStyle.Transient,
Component.For<MyConsumer>().LifeStyle.Transient
);
Assert.Throws<Exception>(() => container.Resolve<MyConsumer>());
}
[Test]
public void CreateWithError_WhatActuallyHappens() //Gives me back a consumer, but ignores MyDependency
{
var container = new WindsorContainer().Register(
Component.For<IMyDependency>().UsingFactoryMethod(() => new MyDependency(true)).LifeStyle.Transient,
Component.For<MyConsumer>().LifeStyle.Transient
);
var consumer = container.Resolve<MyConsumer>();
Assert.IsNotNull(consumer);
Assert.IsNull(consumer.MyDependency); //Basically fails silently!
}
}
An interesting observation, if I use this in my MVC application, I get an internal error from Windsor when calling ReleaseComponent
-- so even though it did not give me back a class with my dependency injected, it still appears to try releasing it.
As far as I know, yes, that's the intended behavior. This isn't specific to factory methods, it works like that for all optional service dependencies. Optional dependencies that throw when resolving are treated as non-resolvable. This is defined in DefaultComponentActivator.ObtainPropertyValue()
Of course, you could always override the default activator with your own if you want to change this behavior.
As well as the option suggested by Mauricio, it is also possible to create a Facility to achieve the expected behaviour as explained on this example page about Facilities.
Here is my implementation which is slightly more concise:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class NonOptionalAttribute : Attribute
{
}
public class NonOptionalPropertiesFacility : AbstractFacility
{
protected override void Init()
{
Kernel.ComponentModelBuilder.AddContributor(new NonOptionalInspector());
}
}
public class NonOptionalInspector : IContributeComponentModelConstruction
{
public void ProcessModel(IKernel kernel, ComponentModel model)
{
foreach (var prop in model.Properties.Where(prop => prop.Property.IsDefined(typeof (NonOptionalAttribute), false)))
{
prop.Dependency.IsOptional = false;
}
}
}
Then just decorate any properties with [NonOptional]
and you will receive an error if there is an issue with construction.
As of Castle Windsor 3.2 there's new a cool addition that is Diagnostic logging in the container.
So if you do this for example in an ASP.NET MVC app:
var logger = _container.Resolve<ILogger>();
((IKernelInternal)_container.Kernel).Logger = logger;
you can redirect the logs caught by Windsor to your configured log4net logger.
The type of information currently being logged includes:
- When Windsor tries to resolve an optional dependency (like property injection), but fails due to an exception, the exception is logged.
- When registering a type by convention and ignoring it due to an existing registration for that type, this fact is logged.
精彩评论