Castle Windsor strange behaviour wth property injection and factory method
Asked Answered
C

3

8

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<IMyDependency>().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.

Cartwheel answered 9/12, 2010 at 12:17 Comment(0)
G
5

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.

Granese answered 9/12, 2010 at 17:34 Comment(1)
Thanks, the answer is spot on! I can work around this in a couple of ways so that's not a problemCartwheel
C
5

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.

Cartwheel answered 10/12, 2010 at 15:15 Comment(1)
Perfect! If anyone reading can't remember the syntax for installing facilities (like me), just call Container.AddFacility<NonOptionalPropertiesFacility>() during your bootstrap routinesPerambulator
S
0

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.
Spectacle answered 8/1, 2014 at 21:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.