Rob Smyth

Wednesday 5 September 2007

Dependency Injection - Ninject

To evaluate Ninject I downloaded the binaries and created a VS project to play with. First question was how to use it. I could not see any doco in the downloads (I only downloaded the binaries) and the web site gave a nice intro but no setup info I could immediately see (I'm impatient). So I tried adding a reference to Ninject.Core.dll. This worked well with the simple web guide.

My test was to implement a dog owner (John Smith) with two dogs. John is a singleton and he names his dogs. Each dog has tail and a mouth that can bite. Pull the tail and he bites! This is designed to test a singleton object with multiple instances of another object. Those objects (the dogs) do in turn have other objects with circular references (dog has tail and tail is attached to dog).

This was easy except that Ninject was unable to resolve a circular dependency (using a constructor and method injection combination). I had hoped that the instances would be created and then the method injection done.

Note: Comment from Nate (see comments below) points out that Ninject does resolve circular dependencies. Looks like my test was wrong, I will retry it again in the next couple of days.

Here is the code:

class Program
{
static void Main(string[] args)
{
IKernel kernel = new StandardKernel(new RunTimeIoCModule());

IJohnSmith johnSmith = (IJohnSmith)kernel.Get();
johnSmith.PatDog("Rover");
johnSmith.PatDog("Rex");

johnSmith.ChangeDogsName("Rover", "Spike");

IJohnSmith johnSmithAgain = kernel.Get();
johnSmithAgain.PatDog("Rover");
johnSmithAgain.PatDog("Rex");

johnSmithAgain.PullTail("Rex");

System.Threading.Thread.Sleep(3000); // just time to see the output
}
}

public class RunTimeIoCModule : StandardModule
{
public override void Load()
{
Bind().To();
Bind().To();
Bind().To();
Bind().To();
}
}

[Transient]
public class Dog : IDog
{
private ITail tail;
private IMouth mouth;
private string name;

[Inject]
public Dog(ITail tail, IMouth mouth)
{
this.tail = tail;
this.tail.SetDog(this);

this.mouth = mouth;
}

public string Name
{
get { return name; }
set { name = value; }
}

public void Pat()
{
Console.WriteLine("Dog '{0}': Pat - thank you", name);
tail.Wag();
}

public void Ouch()
{
Console.WriteLine("Dog '{0}': Ouch!", name);
mouth.Bite();
}

public void PullTail()
{
Console.WriteLine("Dog '{0}': Pulling tail - watch out!", name);
tail.Pull();
}
}

[Transient]
public class Mouth : IMouth
{
public void Bite()
{
Console.WriteLine("Bite!");
}
}

[Transient]
public class Tail : ITail
{
private IDog dog;

public void Wag()
{
Console.WriteLine("Tail Wag");
}

public void Pull()
{
Console.WriteLine("Tail Pull");
dog.Ouch();
}

public void SetDog(IDog dog)
{
this.dog = dog;
}
}

[Singleton]
public class JohnSmith : IJohnSmith
{
private IDog dog1;
private IDog dog2;

[Inject]
public JohnSmith(IDog rover, IDog rex)
{
Console.WriteLine("Hi, my name is John Smith.");

this.dog1 = rover;
this.dog1.Name = "Rover";

this.dog2 = rex;
this.dog2.Name = "Rex";
}

public void ChangeDogsName(string oldName, string newName)
{
IDog dog = GetDog(oldName);

if (dog != null)
{
dog.Name = newName;
}
}

public void PatDog(string name)
{
IDog dog = GetDog(name);

if (dog != null)
{
dog.Pat();
}
}

public void PullTail(string name)
{
IDog dog = GetDog(name);

if (dog != null)
{
dog.PullTail();
}
}

private IDog GetDog(string name)
{
IDog dog;

if (dog1.Name == name)
{
dog = dog1;
}
else if (dog2.Name == name)
{
dog = dog2;
}
else
{
dog = null;
Console.WriteLine("I do not have a dog called '{0}'.", name);
}

return dog;
}
}

I've not published the interfaces to save space. I used interfaces to support TDD.

2 comments:

Nate Kohari said...

Rob: Ninject support circular injection as long as the constructors of the two types don't refer to each other. You might try switching to property injection instead. (Just expose a public property of the type you want injected, and tag it with [Inject].)

Rob Smyth said...

Thanks for the input. I will give it a go.