Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save danielmarbach/326321 to your computer and use it in GitHub Desktop.
Save danielmarbach/326321 to your computer and use it in GitHub Desktop.
/// <summary>
/// Implements a heuristic for ninject property injection.
/// </summary>
public interface IObjectBuilderPropertyHeuristic : IInjectionHeuristic
{
/// <summary>
/// Gets the registered types.
/// </summary>
/// <value>The registered types.</value>
IList<Type> RegisteredTypes
{
get;
}
}
/// <summary>
/// Only injects properties on an instance if that instance has not
/// been previously activated. This forces property injection to occur
/// only once for instances within a scope -- e.g. singleton or within
/// the same request, etc. Instances are removed on deactivation.
/// </summary>
public class NewActivationPropertyInjectStrategy : PropertyInjectionStrategy
{
private readonly HashSet<object> activatedInstances = new HashSet<object>();
/// <summary>
/// Initializes a new instance of the <see cref="NewActivationPropertyInjectStrategy"/> class.
/// </summary>
/// <param name="injectorFactory">The injector factory component.</param>
public NewActivationPropertyInjectStrategy(IInjectorFactory injectorFactory)
: base(injectorFactory)
{
}
/// <summary>
/// Injects values into the properties as described by
/// <see cref="T:Ninject.Planning.Directives.PropertyInjectionDirective"/>s
/// contained in the plan.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="reference">A reference to the instance being
/// activated.</param>
public override void Activate(IContext context, InstanceReference reference)
{
if (this.activatedInstances.Contains(reference.Instance))
{
return; // "Skip" standard activation as it was already done!
}
// Keep track of non-transient activations...
// Note: Maybe this should be
// ScopeCallback == StandardScopeCallbacks.Singleton
if (context.Binding.ScopeCallback != StandardScopeCallbacks.Transient)
{
this.activatedInstances.Add(reference.Instance);
}
base.Activate(context, reference);
}
/// <summary>
/// Contributes to the deactivation of the instance in the specified context.
/// </summary>
/// <param name="context">The context.</param>
/// <param name="reference">A reference to the instance being
/// deactivated.</param>
public override void Deactivate(IContext context, InstanceReference reference)
{
this.activatedInstances.Remove(reference.Instance);
base.Deactivate(context, reference);
}
}
/// <summary>
/// Implementation of IBuilderInternal using the N inject Framework container
/// </summary>
public class NinjectObjectBuilder : IContainer
{
private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
/// <summary>
/// The kernel hold by this object builder.
/// </summary>
private readonly IKernel kernel;
/// <summary>
/// The object builders injection propertyHeuristic for properties.
/// </summary>
private readonly IObjectBuilderPropertyHeuristic propertyHeuristic;
/// <summary>
/// Maps the supported <see cref="ComponentCallModelEnum"/> to the <see cref="StandardScopeCallbacks"/> of ninject.
/// </summary>
private readonly IDictionary<ComponentCallModelEnum, Func<IContext, object>> callModelToScopeMapping =
new Dictionary<ComponentCallModelEnum, Func<IContext, object>>
{
{ ComponentCallModelEnum.Singleton, StandardScopeCallbacks.Singleton },
{ ComponentCallModelEnum.Singlecall, StandardScopeCallbacks.Transient },
};
/// <summary>
/// Initializes a new instance of the <see cref="NinjectObjectBuilder"/> class.
/// </summary>
/// <remarks>
/// Uses the default object builder property <see cref="propertyHeuristic"/>
/// <see cref="ObjectBuilderPropertyHeuristic"/>.
/// </remarks>
/// <param name="kernel">
/// The kernel.
/// </param>
public NinjectObjectBuilder(IKernel kernel)
{
this.kernel = kernel;
this.RegisterNecessaryBindings();
this.propertyHeuristic = this.kernel.Get<IObjectBuilderPropertyHeuristic>();
this.AddCustomPropertyInjectionHeuristic();
this.ReplacePropertyInjectionStrategyWithCustomPropertyInjectionStrategy();
}
/// <summary>
/// Builds the specified type.
/// </summary>
/// <param name="typeToBuild">
/// The type to build.
/// </param>
/// <returns>
/// An instance of the given type.
/// </returns>
public object Build(Type typeToBuild)
{
var output = this.kernel.Get(typeToBuild);
return output;
}
/// <summary>
/// Returns a list of objects instantiated because their type is compatible with the given type.
/// </summary>
/// <param name="typeToBuild">
/// The type to build.
/// </param>
/// <returns>
/// A list of objects
/// </returns>
public IEnumerable<object> BuildAll(Type typeToBuild)
{
var output = this.kernel.GetAll(typeToBuild);
return output;
}
/// <summary>
/// Configures the specified component.
/// </summary>
/// <param name="component">
/// The component.
/// </param>
/// <param name="callModel">
/// The call model.
/// </param>
public void Configure(Type component, ComponentCallModelEnum callModel)
{
if (this.HasComponent(component))
{
Log.DebugFormat(
CultureInfo.InvariantCulture,
"Skipping configuration for {0} and call model {1}",
component.FullName,
callModel);
return;
}
var instanceScope = this.GetInstanceScopeFrom(callModel);
this.BindComponentToItself(component, instanceScope);
this.BindAliasesOfComponentToComponent(component, instanceScope);
Log.DebugFormat(
CultureInfo.InvariantCulture,
"Registering configuration for {0} and call model {1}",
component.FullName,
callModel);
this.propertyHeuristic.RegisteredTypes.Add(component);
}
/// <summary>
/// Configures the property.
/// </summary>
/// <param name="component">
/// The component.
/// </param>
/// <param name="property">
/// The property.
/// </param>
/// <param name="value">
/// The value.
/// </param>
public void ConfigureProperty(Type component, string property, object value)
{
var bindings = this.kernel.GetBindings(component);
if (!bindings.Any())
{
throw new ArgumentException("Component not registered", "component");
}
foreach (var binding in bindings)
{
binding.Parameters.Add(new PropertyValue(property, value));
}
}
/// <summary>
/// Registers the singleton.
/// </summary>
/// <param name="lookupType">
/// Type lookup type.
/// </param>
/// <param name="instance">
/// The instance.
/// </param>
public void RegisterSingleton(Type lookupType, object instance)
{
this.kernel.Bind(lookupType).ToConstant(instance);
}
/// <summary>
/// Determines whether the specified component type has a component.
/// </summary>
/// <param name="componentType">
/// Type of the component.
/// </param>
/// <returns>
/// <c>true</c> if the specified component type has a component; otherwise, <c>false</c>.
/// </returns>
public bool HasComponent(Type componentType)
{
var bindings = this.kernel.GetBindings(componentType);
return bindings.Any();
}
/// <summary>
/// Gets all service types of a given component.
/// </summary>
/// <param name="component">The component.</param>
/// <returns>All service types.</returns>
private static IEnumerable<Type> GetAllServices(Type component)
{
return component.GetInterfaces();
}
/// <summary>
/// Gets the instance scope from call model.
/// </summary>
/// <param name="callModel">
/// The call model.
/// </param>
/// <returns>
/// The instance scope
/// </returns>
private Func<IContext, object> GetInstanceScopeFrom(ComponentCallModelEnum callModel)
{
Func<IContext, object> scope;
if (!this.callModelToScopeMapping.TryGetValue(callModel, out scope))
{
throw new ArgumentException("The call model is not supported", "callModel");
}
return scope;
}
/// <summary>
/// Binds the aliases of component to component with the given <paramref name="instanceScope"/>.
/// </summary>
/// <param name="component">
/// The component.
/// </param>
/// <param name="instanceScope">
/// The instance scope.
/// </param>
private void BindAliasesOfComponentToComponent(Type component, Func<IContext, object> instanceScope)
{
var services = GetAllServices(component).Where(t => t != component);
foreach (var service in services)
{
this.kernel.Bind(service).ToMethod(ctx => ctx.Kernel.Get(component))
.InScope(instanceScope);
}
}
/// <summary>
/// Binds the component to itself with the given <paramref name="instanceScope"/>.
/// </summary>
/// <param name="component">
/// The component.
/// </param>
/// <param name="instanceScope">
/// The instance scope.
/// </param>
private void BindComponentToItself(Type component, Func<IContext, object> instanceScope)
{
this.kernel.Bind(component).ToSelf()
.InScope(instanceScope);
}
/// <summary>
/// Adds the custom property injection heuristic.
/// </summary>
private void AddCustomPropertyInjectionHeuristic()
{
this.kernel.Components.Get<ISelector>().InjectionHeuristics.Add(
this.kernel.Get<IObjectBuilderPropertyHeuristic>());
}
/// <summary>
/// Registers the necessary bindings.
/// </summary>
private void RegisterNecessaryBindings()
{
this.kernel.Bind<IContainer>().ToConstant(this).InSingletonScope();
this.kernel.Bind<NewActivationPropertyInjectStrategy>().ToSelf()
.InSingletonScope()
.WithPropertyValue("Settings", ctx => ctx.Kernel.Settings);
this.kernel.Bind<IObjectBuilderPropertyHeuristic>().To<ObjectBuilderPropertyHeuristic>()
.InSingletonScope()
.WithPropertyValue("Settings", ctx => ctx.Kernel.Settings);
this.kernel.Bind<IInjectorFactory>().ToMethod(ctx => ctx.Kernel.Components.Get<IInjectorFactory>());
}
/// <summary>
/// Replaces the default property injection strategy with custom property injection strategy.
/// </summary>
private void ReplacePropertyInjectionStrategyWithCustomPropertyInjectionStrategy()
{
IList<IActivationStrategy> activationStrategies = this.kernel.Components.Get<IPipeline>().Strategies;
IList<IActivationStrategy> copiedStrategies = new List<IActivationStrategy>(
activationStrategies.Where(strategy => !strategy.GetType().Equals(typeof(PropertyInjectionStrategy)))
.Union(new List<IActivationStrategy> { this.kernel.Get<NewActivationPropertyInjectStrategy>() }));
activationStrategies.Clear();
copiedStrategies.ToList().ForEach(activationStrategies.Add);
}
}
/// <summary>
/// The static class which holds <see cref="NServiceBus"/> extensions methods.
/// </summary>
public static class NinjectObjectBuilderConfig
{
/// <summary>
/// Instructs <see cref="NServiceBus"/> to use the provided kernel
/// </summary>
/// <param name="config">The extended Configure.</param>
/// <param name="kernel">The kernel.</param>
/// <returns>The Configure.</returns>
public static Configure NinjectBuilder(this Configure config, IKernel kernel)
{
ConfigureCommon.With(config, new NinjectObjectBuilder(kernel));
return config;
}
}
/// <summary>
/// Tests the behavior of the <see cref="NinjectObjectBuilder"/>.
/// </summary>
public class NinjectObjectBuilderTest
{
private readonly IKernel kernelUsedByTestee;
private readonly NinjectObjectBuilder testee;
/// <summary>
/// Initializes a new instance of the <see cref="NinjectObjectBuilderTest"/> class.
/// </summary>
public NinjectObjectBuilderTest()
{
this.kernelUsedByTestee = new StandardKernel();
this.testee = new NinjectObjectBuilder(this.kernelUsedByTestee);
}
/// <summary>
/// TestInterface which allows to retrieve all instances implementing this interface.
/// </summary>
private interface ITestInterface
{
}
/// <summary>
/// The constructor must register the instance of itself on the kernelUsedByTestee.
/// </summary>
[Fact]
public void Constructor_MustRegisterInstanceOnKernel()
{
var container = this.kernelUsedByTestee.Get<IContainer>();
Assert.NotNull(container);
Assert.Same(this.testee, container);
}
/// <summary>
/// <see cref="ObjectBuilderPropertyHeuristic"/> must be added to the <see cref="ISelector"/> because this
/// overrides the standard property injection behavior of n inject.
/// </summary>
[Fact]
public void Constructor_MustRegisterObjectBuilderPropertyHeuristic()
{
var heuristics = this.kernelUsedByTestee.Components.Get<ISelector>().InjectionHeuristics;
Assert.True(heuristics.Select(heuristic => heuristic.GetType()).Contains(typeof(ObjectBuilderPropertyHeuristic)));
}
/// <summary>
/// The <see cref="NewActivationPropertyInjectStrategy"/> must be added to the pipeline's strategies.
/// </summary>
[Fact]
public void Constructor_MustRegisterNewActivationPropertyInjectionStrategy()
{
var strategies = this.kernelUsedByTestee.Components.Get<IPipeline>().Strategies;
Assert.Contains(typeof(NewActivationPropertyInjectStrategy), strategies.Select(strategy => strategy.GetType()));
}
/// <summary>
/// All the standard strategies of ninject must be kept expect the <see cref="PropertyInjectionStrategy"/> must
/// be replaced.
/// </summary>
[Fact]
public void Constructor_MustHonorNinjectStandardStrategiesExpectPropertyStrategy()
{
var standardKernel = new StandardKernel();
var standardStrategies = standardKernel.Components.Get<IPipeline>().Strategies;
var standardStrategyTypes = standardStrategies.Select(strategy => strategy.GetType());
var strategies = this.kernelUsedByTestee.Components.Get<IPipeline>().Strategies;
var strategyTypes = strategies.Select(strategy => strategy.GetType());
var standardStrategyTypesWithoutPropertyInjectionStrategy = standardStrategyTypes
.Where(strategytype => !strategytype.Equals(typeof(PropertyInjectionStrategy)));
var strategyTypesWithoutNewActionavtionPropertyInjectionStrategy = strategyTypes
.Where(strategytype => !strategytype.Equals(typeof(NewActivationPropertyInjectStrategy)));
Assert.DoesNotContain(typeof(PropertyInjectionStrategy), strategyTypes);
Assert.Equal(standardStrategyTypesWithoutPropertyInjectionStrategy, strategyTypesWithoutNewActionavtionPropertyInjectionStrategy);
}
/// <summary>
/// When the bindings cannot be fulfilled an <see cref="ActivationException"/> must be thrown.
/// </summary>
[Fact]
public void Build_WhenNoBindingsPresent_MustThrowActivationException()
{
Assert.Throws<ActivationException>(() => { this.testee.Build(typeof(TestClass)); });
}
/// <summary>
/// When bindings are present an new instance must be build up.
/// </summary>
[Fact]
public void Build_WhenBindingPresent_MustCreateInstance()
{
const string ConstructorArgument = "TestDependency";
this.DefineCustomBindingsOnKernel(ConstructorArgument);
var testObject = this.testee.Build(typeof(TestClass)) as TestClass;
Assert.NotNull(testObject);
Assert.Equal(ConstructorArgument, testObject.Dependency.Name);
}
/// <summary>
/// BuildAll must throw an <see cref="ActivationException"/> when no bindings are present.
/// </summary>
[Fact]
public void BuildAll_WhenNoBindingsPresent_MustThrowActivationExceptionWhenIterated()
{
Assert.Throws<ActivationException>(() => { this.testee.BuildAll(typeof(TestClass)).Any(); });
}
/// <summary>
/// When bindings are present all bindings defined for the requested type must be instantiated.
/// </summary>
[Fact]
public void BuildAll_WhenBindingsPresent_MustCreateAvailableInstances()
{
this.DefineCustomBindingsOnKernel("TestDependency");
this.kernelUsedByTestee.Bind<ITestInterface>().ToMethod(ctx => ctx.Kernel.Get<TestClass>());
this.kernelUsedByTestee.Bind<ITestInterface>().ToMethod(ctx => ctx.Kernel.Get<Dependency>());
var testInstances = this.testee.BuildAll(typeof(ITestInterface));
Assert.Equal(2, testInstances.Count());
}
/// <summary>
/// When no bindings for a requested type are available the has component request must return
/// <see langword="false"/>.
/// </summary>
[Fact]
public void HasComponent_WhenNoBindingsPresent_MustReturnFalse()
{
Assert.False(this.testee.HasComponent(typeof(TestClass)));
}
/// <summary>
/// When bindings for a requested type are available the has component request must return
/// <see langword="true"/>.
/// </summary>
[Fact]
public void HasComponent_WhenBindingsPresent_MustReturnTrue()
{
this.DefineCustomBindingsOnKernel("TestDependency");
Assert.True(this.testee.HasComponent(typeof(TestClass)));
}
/// <summary>
/// When registering singletons the some instance must be returned on every request.
/// </summary>
[Fact]
public void RegisterSingleton_MustBindToConstant()
{
TestClass testObject = new TestClass(null);
this.testee.RegisterSingleton(typeof(TestClass), testObject);
var firstResultObject = this.kernelUsedByTestee.Get<TestClass>();
var secondResultObject = this.kernelUsedByTestee.Get<TestClass>();
Assert.Same(testObject, firstResultObject);
Assert.Same(testObject, secondResultObject);
}
/// <summary>
/// When no bindings are defined for a given type an argument exception must be thrown.
/// </summary>
[Fact]
public void ConfigureProperty_WhenNoBindingsAreDefined_MustThrowArgumentException()
{
Assert.Throws<ArgumentException>(() => this.testee.ConfigureProperty(typeof(TestClass), "Dependency", new Dependency(null)));
}
/// <summary>
/// When bindings are defined for a given type a property value must be added for each binding.
/// </summary>
[Fact]
public void ConfigureProperty_WhenBindingsAreDefined_PropertyParameterMustBeAdded()
{
this.DefineCustomBindingsOnKernel(string.Empty);
this.testee.ConfigureProperty(typeof(TestClass), "Dependency", new Dependency(null));
var bindings = this.kernelUsedByTestee.GetBindings(typeof(TestClass));
Assert.Equal(1, bindings.Single().Parameters.Count);
Assert.True(bindings.Single().Parameters.Single() is PropertyValue);
}
/// <summary>
/// Configuration of a component with <see cref="ComponentCallModelEnum.Singleton"/> must register type with
/// singleton scope.
/// </summary>
[Fact]
public void Configure_WithSingletonCallModel_MustRegisterSingletonScope()
{
this.kernelUsedByTestee.Bind<Dependency>().ToSelf().WithConstructorArgument("name", string.Empty);
this.testee.Configure(typeof(TestClass), ComponentCallModelEnum.Singleton);
var firstInstance = this.kernelUsedByTestee.Get<TestClass>();
var secondInstance = this.kernelUsedByTestee.Get<TestClass>();
Assert.Same(firstInstance, secondInstance);
}
/// <summary>
/// Configuration of a component with <see cref="ComponentCallModelEnum.Singlecall"/> must register type with
/// transient scope.
/// </summary>
[Fact]
public void Configure_WithSingleCallModel_MustRegisterTransientScope()
{
this.kernelUsedByTestee.Bind<Dependency>().ToSelf().WithConstructorArgument("name", string.Empty);
this.testee.Configure(typeof(TestClass), ComponentCallModelEnum.Singlecall);
var firstInstance = this.kernelUsedByTestee.Get<TestClass>();
var secondInstance = this.kernelUsedByTestee.Get<TestClass>();
Assert.NotSame(firstInstance, secondInstance);
}
/// <summary>
/// Configuration of a call model which is not supported must throw <see cref="ArgumentException"/>.
/// </summary>
[Fact]
public void Configure_WithUnsupportedCallModel_MustThrowArgumentException()
{
Assert.Throws<ArgumentException>(() => this.testee.Configure(typeof(TestClass), ComponentCallModelEnum.None));
}
/// <summary>
/// Properties set on duplicate registrations should not be discarded.
/// </summary>
/// <remarks>These tests are from NServiceBus tests for object builders.</remarks>
[Fact]
public void Properties_SetOnDuplicateRegistrations_ShouldNotBeDiscarded()
{
this.testee.Configure(typeof(DuplicateClass), ComponentCallModelEnum.Singleton);
this.testee.ConfigureProperty(typeof(DuplicateClass), "SomeProperty", true);
this.testee.Configure(typeof(DuplicateClass), ComponentCallModelEnum.Singleton);
this.testee.ConfigureProperty(typeof(DuplicateClass), "AnotherProperty", true);
var component = (DuplicateClass) this.testee.Build(typeof(DuplicateClass));
Assert.True(component.SomeProperty);
Assert.True(component.AnotherProperty);
}
/// <summary>
/// Multiple registrations of the same component should be allowed.
/// </summary>
/// <remarks>These tests are from NServiceBus tests for object builders.</remarks>
[Fact]
public void MultipleRegistrationsOfTheSameComponent_ShouldBeAllowed()
{
this.testee.Configure(typeof(DuplicateClass), ComponentCallModelEnum.Singlecall);
this.testee.Configure(typeof(DuplicateClass), ComponentCallModelEnum.Singlecall);
var builded = this.testee.BuildAll(typeof(DuplicateClass));
Assert.Equal(1, builded.Count());
}
/// <summary>
/// Defines the custom bindings on the kernelUsedByTestee.
/// </summary>
/// <param name="constructorArgument">The constructor argument.</param>
private void DefineCustomBindingsOnKernel(string constructorArgument)
{
this.kernelUsedByTestee.Bind<TestClass>().ToSelf();
this.kernelUsedByTestee.Bind<Dependency>().ToSelf().WithConstructorArgument("name", constructorArgument);
}
/// <summary>
/// Test class from NServiceBus sources.
/// </summary>
private class DuplicateClass
{
/// <summary>
/// Gets or sets a value indicating whether some property is set.
/// </summary>
/// <value><c>true</c> if some property is set; otherwise, <c>false</c>.</value>
public bool SomeProperty { get; set; }
/// <summary>
/// Gets or sets a value indicating whether another property is set.
/// </summary>
/// <value><c>true</c> if another property is set; otherwise, <c>false</c>.</value>
public bool AnotherProperty { get; set; }
}
/// <summary>
/// Test class which is only used in this test.
/// </summary>
private class TestClass : ITestInterface
{
/// <summary>
/// Initializes a new instance of the <see cref="TestClass"/> class.
/// </summary>
/// <param name="dependency">The dependency.</param>
public TestClass(Dependency dependency)
{
this.Dependency = dependency;
}
/// <summary>
/// Gets the dependency.
/// </summary>
/// <value>The dependency.</value>
public Dependency Dependency
{
get;
private set;
}
}
/// <summary>
/// Dependency class which is only used in this test.
/// </summary>
private class Dependency : ITestInterface
{
/// <summary>
/// Initializes a new instance of the <see cref="Dependency"/> class.
/// </summary>
/// <param name="name">The name of this dependency.</param>
public Dependency(string name)
{
this.Name = name;
}
/// <summary>
/// Gets the name.
/// </summary>
/// <value>The name of this dependency.</value>
public string Name
{
get;
private set;
}
}
}
/// <summary>
/// Implements an more aggressive injection heuristic.
/// </summary>
internal class ObjectBuilderPropertyHeuristic : IObjectBuilderPropertyHeuristic
{
/// <summary>
/// Initializes a new instance of the <see cref="ObjectBuilderPropertyHeuristic"/> class.
/// </summary>
public ObjectBuilderPropertyHeuristic()
{
this.RegisteredTypes = new List<Type>();
}
/// <summary>
/// Gets the registered types.
/// </summary>
/// <value>The registered types.</value>
public IList<Type> RegisteredTypes { get; private set; }
/// <summary>
/// Gets or sets the settings.
/// </summary>
/// <value>The settings.</value>
public INinjectSettings Settings
{
get;
set;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Determines whether a given type should be injected.
/// </summary>
/// <param name="member">The member info to check.</param>
/// <returns><see langword="true"/> if a given type needs to be injected; otherwise <see langword="false"/>.
/// </returns>
public bool ShouldInject(MemberInfo member)
{
var propertyInfo = member as PropertyInfo;
if (propertyInfo == null)
{
return false;
}
return this.RegisteredTypes.Where(x => propertyInfo.DeclaringType.IsAssignableFrom(x)).Any()
&& this.RegisteredTypes.Where(x => propertyInfo.PropertyType.IsAssignableFrom(x)).Any()
&& propertyInfo.CanWrite;
}
/// <summary>
/// Releases unmanaged and - optionally - managed resources
/// </summary>
/// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release
/// only unmanaged resources.</param>
protected virtual void Dispose(bool disposing)
{
this.RegisteredTypes.Clear();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment