Question

I have a similar situation to this in my code, where I have a class that descends from two ancestor abstract classes, like so:

BaseAbstractClassExample <|-- AbstractClassExample <|-- ConcreteClassExample

I did this to extend an abstract class defined in the framework. While I'm aware there are other design patterns that might be better suited to my situation, I'm curious why this convention-based binding doesn't work.

using Ninject.Extensions.Conventions; 

public abstract class BaseAbstractClassExample
{
    public abstract int Number { get; set; }
}

public abstract class AbstractClassExample : BaseAbstractClassExample
{
    public abstract bool Flag { get; set; }
}

public class ConcreteClassExample : AbstractClassExample
{
    public override int Number { get; set; }
    public override bool Flag { get; set; }
}

[TestMethod]
public void Concrete_classes_are_bound_to_grandfathers()
{
    kernel.Bind(x => x.FromThisAssembly()
                        .SelectAllClasses().InheritedFrom<BaseAbstractClassExample>()                            
                        .BindBase());

    AssertCanResolveBindingToType<ConcreteClassExample, ConcreteClassExample>(); // pass
    AssertCanResolveBindingToType<AbstractClassExample, ConcreteClassExample>(); // pass
    AssertCanResolveBindingToType<BaseAbstractClassExample, ConcreteClassExample>(); // fail
}

Here's the assert method I wrote to test bindings, which is tangential to my question.

private static void AssertCanResolveBindingToType<TRequestedType, TExpectedType>(params IParameter[] constructorParameters)
    {
        if (!typeof(TRequestedType).IsAssignableFrom(typeof(TExpectedType)))
            Assert.Fail("{0} is not assignable from {1}, this binding wouldn't work anyway", typeof(TRequestedType), typeof(TExpectedType));

        IEnumerable<TRequestedType> result = kernel.GetAll<TRequestedType>(constructorParameters);
        var requestedTypes = result as TRequestedType[] ?? result.ToArray();
        Assert.IsTrue(requestedTypes.Any(), "There are no bindings for {0} at all", typeof (TRequestedType));
        Assert.IsTrue(requestedTypes.OfType<TExpectedType>().Any(),
                      "There are no bindings for {0} of the expected type {1}, bound types are: {2}", 
                      typeof (TRequestedType), typeof (TExpectedType),
                      string.Join(", ", requestedTypes.Select(x => x.GetType().ToString()).Distinct()));
    }

When I try the unit test above, it asserts with my custom message "There are no bindings for BaseAbstractClassExample at all", which shows that the binding to AbstractClassExample is working as expected, but not the one to BaseAbstractClassExample.

Edit: I wrote a method BindAllBaseClasses() that provides this functionality. I submitted a pull request and it was approved, so this functionality is now available in the Ninject extensions conventions library.

Was it helpful?

Solution 2

This is by design. When you use SelectAllClasses it doesn't select Abstract classes because the filter used is the following:

  public IJoinFilterWhereExcludeIncludeBindSyntax SelectAllClasses()
        {
            return this.SelectTypes(t => t.IsClass && !t.IsAbstract);
        }

Try to use

 public IJoinFilterWhereExcludeIncludeBindSyntax SelectAllIncludingAbstractClasses()
        {
            return this.SelectTypes(t => t.IsClass);
        }

Although this doesn't explain to me why you are able to resolve AbstractClassExample. This might be a bug. Do you mind raising an issue on the convention extension? Another cause could be that the BaseBindingGenerator doesn't include abstract classes.

OTHER TIPS

As Daniel explained SelectAllClasses() selects all classes that are not abstract. It makes no sense to select abstract classes because it is not possible to create an instance of them.

So in your case it selects ConcreteClassExample.

Then BindBase() tells that a binding of the base class to the selected class shall be added. In your case it is:

Bind<AbstractClassExample>().To<ConcreteClassExample>();

Now you can resolve AbstractClassExample because there is a binding and ConcreteClassExample because it is self bindabel and Ninject will create an implicit self binding even if there is no configured one.

You can't resolve BaseAbstractClassExample because there is neither a binding for it nor is it self bindable because it is abstract.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top