We have several cases where we are providing services in code libraries where we know scoping and lifetime rules for the service providers in the code library. We would like to configure that information in the library itself without having to have that knowledge bubbled up to the composition root.
I have been unable to figure out if it's possible to implement this with the current version of Ninject.
using System;
using System.Diagnostics.CodeAnalysis;
using Ninject;
using Ninject.Extensions.Conventions;
using NUnit.Framework;
using Ninject.Modules;
[TestFixture]
public class Spike
{
private IKernel kernel;
[SetUp]
public void SetUp()
{
this.kernel = new StandardKernel();
this.kernel.Load(new Registry());
this.kernel.Bind(x => x
.FromThisAssembly()
.SelectAllClasses()
.BindAllInterfaces()
);
}
[TearDown]
public void TearDown()
{
Thing1.ResetCounts();
}
[Test]
public void GetThing1AndThing2()
{
// arrange
var thing1 = this.kernel.Get<Thing1>();
var thing2 = this.kernel.Get<Thing1>();
// act
thing1.DoTheWork();
thing2.DoTheWork();
// assert
Assert.AreEqual(1, Thing1.ConstructorCount, "wrong number of constructor invocations");
Assert.AreEqual(2, Thing1.DoTheWorkCount, "wrong number of method invocations");
}
[Test]
public void GetIThing1AndIThing2()
{
// arrange
var thing1 = this.kernel.Get<IThing1>();
var thing2 = this.kernel.Get<IThing1>();
// act
thing1.DoTheWork();
thing2.DoTheWork();
// assert
Assert.AreEqual(1, Thing1.ConstructorCount, "wrong number of constructor invocations");
Assert.AreEqual(2, Thing1.DoTheWorkCount, "wrong number of method invocations");
}
public class Registry : NinjectModule
{
public override void Load()
{
Bind<Thing1>().ToSelf().InSingletonScope();
}
}
public interface IThing1
{
void DoTheWork();
}
public class Thing1 : IThing1
{
public static int ConstructorCount { get; set; }
public static int DoTheWorkCount { get; set; }
public Thing1()
{
Console.WriteLine("Thing1.ctor underway");
++Thing1.ConstructorCount;
}
public void DoTheWork()
{
Console.WriteLine("Thing1.DoTheWork underway");
++Thing1.DoTheWorkCount;
}
public static void ResetCounts()
{
Thing1.ConstructorCount = 0;
Thing1.DoTheWorkCount = 0;
}
}
}
In this test case, the ilbrary is represented by the Registry
, Thing1
, and IThing1
classes. The user of the library is the test fixture, where the Spike.SetUp()
method shows the code we'd ideally like the library user to write (where they'd pass in a path containing the dll instead of new-ing up a Registry
object).
With the code as written, fetching the Thing1
service multiple times in Spike.GetThing1AndThing2()
exhibits the desired singleton behavior. Fetching the Thing1
service multiple times via its published interface as in Spike.GetIThing1AndIThing2()
does not exhibit singleton behavior but rather constructs two separate Thing1
objects.
So is it possible to do what I'm asking: to specify the singleton behavior in the DLL itself while having the scan performed when the composition root is formed?
You need to introduce conventions. E.g. Add an attribute specifying the scope or use a naming convention so that you can identify the scope from the name.
Then setup the binding conventions correctly. E.g
this.kernel.Bind(x => x
.FromThisAssembly()
.SelectAllClasses()
.WithAttribute<SingletonAttribute>()
.BindAllInterfaces()
.Configure(binding => binding.InSingletonScope());
this.kernel.Bind(x => x
.FromThisAssembly()
.SelectAllClasses()
.WithAttribute<TransientAttribute>()
.BindAllInterfaces());