Requiring an attribute for CastleWindsor property injection

234 Views Asked by At

Although I prefer constructor injection, I want developers on my team to be able to use property injection as long as they explicitly mark the property as injectable with an attribute. According to CastleWindsor documentation here, this should be possible by creating a contributor and then calling AddContributor with it through the Kernel. I cannot seem to get this to work. All public properties (even those without the attribute) are injected.

When I set a breakpoint in my contributor ProcessModel method, it sets all properties with the attribute to mandatory (IsOptional = false) and skips over any properties that do not have the attribute. But despite this, the properties without the attribute still get injected. What am I doing wrong?

Below is my code:

Contributor

public class PropertyInjectionContributor : IContributeComponentModelConstruction
{
    public void ProcessModel(IKernel kernel, ComponentModel model)
    {
        model.Properties
            .Where(p => p.Property.IsDefined(typeof(InjectedAttribute)))
            .All(p => p.Dependency.IsOptional = false);
    }
}

Attribute

[AttributeUsage(AttributeTargets.Property)]
public class InjectedAttribute : Attribute { }

Add contributor to Kernel.ComponentModelBuilder and register components

public class Program
{
    public static void Main(string[] args)
    {
        var container = new WindsorContainer();

        container.Kernel.ComponentModelBuilder.AddContributor(new PropertyInjectionContributor());

        container.Register(Component.For<IClass1>()
            .ImplementedBy<Class1>()
            .LifestyleTransient());

        container.Register(Component.For<IClass2>()
            .ImplementedBy<Class2>()
            .LifestyleTransient());

        var class1 = container.Resolve<IClass1>();
        class1.DoSomething();
    }
}

Class1

public class Class1 : IClass1
{
    //[Injected]
    public IClass2 Class2 { get; set; }

    public void DoSomething()
    {
        Class2.DoSomething();
    }
}

The Class2 property within Class1 is injected even though it is not decorated with the Injected attribute.

My current workaround for this is to remove CastleWindsor's PropertiesDependenciesModelInspector and replace it with an implementation that forces IsOptional to be false. It works but it repeats a lot of CW's code. I'd prefer to use the simple approach above if I can only get it to work!

2

There are 2 best solutions below

1
On

You're only setting IsOptional if the property has the attribute. You're not doing anything for properties without the attribute. For all those properties you don't want injected, you'd need to mark them as not injectable. There is an attribute that already does this: [DoNotWire].

0
On

In case anyone out there runs into this same issue, I was able to solve this with the following solution.

Some caveats:

  • Property injection is generally considered a code smell.
  • This code solution very well could be completely stale since I'm posting this 5 years later and have moved on from the legacy .NET Framework code base where I was implementing this.
  • .NET has their own way of doing DI now which is perfectly sufficient.

My method of solving this problem was to provide a replacement for Castle Windsor's PropertiesDependenciesModelInspector, register it through a custom Facility, and add the Facility to the WindsorContainer.

Replace PropertiesDependenciesModelInspector

public class PropertyInjectionContributor : IContributeComponentModelConstruction
{
    public void ProcessModel(IKernel kernel, ComponentModel model)
    {
        var properties = GetProperties(model);
        if (properties.Count > 0)
        {
            properties.ForEach(p => model.AddProperty(BuildDependency(p)));
        }
    }

    private static PropertySet BuildDependency(PropertyInfo property)
    {
        var dependency = new PropertyDependencyModel(property, false);
        return new PropertySet(property, dependency);
    }

    private static List<PropertyInfo> GetProperties(ComponentModel model)
    {
        var bindingFlags = model.InspectionBehavior == PropertiesInspectionBehavior.DeclaredOnly
            ? BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly
            : BindingFlags.Public | BindingFlags.Instance;

        var properties = model.Implementation.GetProperties(bindingFlags);
        return properties.Where(IsValidPropertyDependency).ToList();
    }

    private static bool IsValidPropertyDependency(PropertyInfo property)
    {
        return IsSettable(property) && HasParameters(property) == false && HasInjectedAttribute(property);
    }

    private static bool IsSettable(PropertyInfo property)
    {
        return property.CanWrite && property.GetSetMethod() != null;
    }

    private static bool HasParameters(PropertyInfo property)
    {
        var indexerParams = property.GetIndexParameters();
        return indexerParams.Length != 0;
    }

    private static bool HasInjectedAttribute(MemberInfo property)
    {
        return property.IsDefined(typeof(InjectedAttribute));
    }
}

Custom Facility

public class PropertyInjectionFacility : AbstractFacility
{
    protected override void Init()
    {
        var propInjector = Kernel.ComponentModelBuilder
            .Contributors
            .OfType<PropertiesDependenciesModelInspector>()
            .Single();

        Kernel.ComponentModelBuilder.RemoveContributor(propInjector);

        Kernel.ComponentModelBuilder.AddContributor(new PropertyInjectionContributor());
    }
}

Add Facility to WindsorContainer

var container = new WindsorContainer();
container.AddFacility(new PropertyInjectionFacility());