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.
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.