by Damian
27. November 2011 09:23
Say you interface where you wish to property / method inject a service when activating implementors:
public interface IServiceConsumer
{
[Inject]
ISomeService Service { get; set; }
[Inject]
void SetService(ISomeService service);
}
The problem is that implementors of this interface will not 'inherit' these [Inject] attributes, by design. Thus Ninject won't see the attribute and won't inject when activating.
To solve this I've created a custom injection heuristic that inspects a member type's interface map to see if the member of concern:
- implements an interface method that ithat has the desired attribute.
- is a interface property set_ method whose corresponding property has the desired attribute.
It also works with your own custom injection attribute if you are using one with via the T type paramater.
public class InterfaceInjectAttributeInjectionHeuristic<T> : NinjectComponent, IInjectionHeuristic
where T:Attribute
{
public bool ShouldInject(MemberInfo member)
{
foreach (Type @interface in member.DeclaringType.GetInterfaces())
{
InterfaceMapping interfaceMapping = member.ReflectedType.GetInterfaceMap(@interface);
int index = Array.IndexOf(interfaceMapping.TargetMethods, member);
if (index < 0)
{
continue;
}
MethodInfo interfaceMethod = interfaceMapping.InterfaceMethods[index];
Contract.Assume(interfaceMethod != null);
member = GetProperty(interfaceMethod) ?? interfaceMethod;
return member.GetCustomAttributes(true).Any(a => a.GetType() == typeof(T));
}
return false;
}
static MemberInfo GetProperty(MethodInfo method)
{
bool takesArg = method.GetParameters().Length == 1;
bool hasReturn = method.ReturnType != typeof(void);
if (takesArg == hasReturn)
{
return null;
}
return takesArg ? method.DeclaringType.GetProperties().Where(prop => prop.GetSetMethod() == method).FirstOrDefault() : null;
}
}
Now whether this is actually a good idea or not, I'll leave it you to decide, but it helped me solve a problem. Feedback always welcome.
For completeness here are my tests:
using Ninject;
using Ninject.Selection.Heuristics;
using Xunit;
public class InterfaceInjectAttributeInjectionHeuristicTests
{
public class Given_a_kernel_resolved_component
{
private readonly MyComponent _sut;
public Given_a_kernel_resolved_component()
{
var standardKernel = new StandardKernel();
standardKernel.Components.Add<IInjectionHeuristic, InterfaceInjectAttributeInjectionHeuristic<InjectAttribute>>();
standardKernel.Bind<ISomeService>().To<SomeServiceImpl>();
_sut = standardKernel.Get<MyComponent>();
}
[Fact]
public void Then_Service_should_not_be_null()
{
Assert.NotNull(_sut.Service);
}
[Fact]
public void Then_SetSerervice_should_be_called()
{
Assert.True(_sut.SetServiceCalled);
}
}
}
public interface ISomeService {}
public interface IServiceConsumer
{
[Inject]
ISomeService Service { get; set; }
[Inject]
void SetService(ISomeService service);
}
public class SomeServiceImpl : ISomeService { }
public class MyComponent : IServiceConsumer
{
public ISomeService Service { get; set; }
public void SetService(ISomeService service)
{
SetServiceCalled = service != null;
}
public bool SetServiceCalled { get; private set; }
}