Damian Hickey

Mostly software and .NET related. Mostly.

Ninject's InjectAttribute on an interface member

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:

  1. implements an interface method that ithat has the desired attribute.
  2. 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; }
}