Welcome to another episode of the 'Do-It-Yourself series'. DependencyResolver, as its name suggests, is an open source .NET Attribute-hungry dependency resolver, and it is based on my experience using Ninject. If you are not familiar with the 'Dependency Injection/Inversion of Control' pattern I suggest you read the following article first:
Inversion of Control @ msdn
Note: The best way to follow these tutorials is to read this page along with the source code, in fact, I believe the whole source code is self-explanatory, but here I'll be trying to provide some guidance for the less experienced.
Let's get started. When using Auto Binding we need to tell DependencyResolver what dependencies should be resolved by using the [AutoResolved] attribute, and also what classes will act as services to resolve dependencies (see the [ResolvesDependency] attribute). Take a look at the following code:
1: [TestClass]
2: public class Tutorial01a
3: {
4: public class AnimalCage
5: {
6: public AnimalCage()
7: {
8: Animal = null;
9: }
10:
11: [AutoResolved]
12: public IAnimal Animal { get; set; }
13: }
14:
15: [TestMethod]
16: public void TestMethod1a()
17: {
18: var dependencyResolver = new Leos.DependencyResolver.DependencyResolver("DependencyResolverTests_Tutorial01a", new Logger());
19: var animalCage = new AnimalCage();
20:
21: try
22: {
23: dependencyResolver.ResolveDependencies(animalCage);
24: Assert.Fail("Expected exception of type 'ServiceNotFoundException'");
25: }
26: catch(ServiceNotFoundException)
27: {
28: // correct! A service could not be found since we never defined a class to resolve the 'IAnimal' dependency
29: }
30: }
So far, we have defined our AnimalCage class, which contains an IAnimal dependency:
11: [AutoResolved]
12: public IAnimal Animal { get; set; }
We have specified the namespace where DependencyResolver should look for bindings:
18: var dependencyResolver = new Leos.DependencyResolver.DependencyResolver("DependencyResolverTests_Tutorial01", new Logger());
And now the moment of truth, let's see if it can resolve the dependencies on the AnimalCage object (can you guess what will happen next?):
23: dependencyResolver.ResolveDependencies(animalCage);
Nope, it couldn't resolve the dependency. If you run the Test a 'ServiceNotFoundException' will be thrown, which is expected since we never defined a class to resolve the 'IAnimal' dependency, and how do we do that? Let's take a look at Tutorial #1b.
This time we are going to use the same code from Tutorial #1a, plus the following Class definition (notice the use of the [ResolvesDependency] attribute):
4: [ResolvesDependency]
5: public class Dog : IAnimal
6: {
7: public void MakeSound()
8: {
9: Console.WriteLine("woof woof!");
10: }
11: }
Run the Test and it will be successful, the IAnimal dependency will be resolved using the 'Dog' service above.
This lesson explains how to achieve 'Contextual binding' using DependencyResolver. Let's go back for a moment to Tutorial #1b and see how we are binding the 'Dog' service to 'IAnimal' type:
4: [ResolvesDependency]
5: public class Dog : IAnimal
6: {
7: public void MakeSound()
8: {
9: Console.WriteLine("woof woof!");
10: }
11: }
It is easy to see that at this point if dependencies are resolved for an AnimalCage object then the IAnimal dependency will be resolved to 'Dog'. But what if we want to bind two or more Services to IAnimal? And how do we specify what Service type is expected?
First we need to define an enum to specify all of our Service types, in this tutorial we will be able to bind 'Dog' & 'Cat' services to IAnimal.
4: public enum AnimalType { Dog = 0, Cat = 1 };
Now it's time to bind each Class to IAnimal type and the proper configuration:
6: [ResolvesDependency(typeof(AnimalType), (int)AnimalType.Dog)]
7: public class Dog : IAnimal
8: {
9: public void MakeSound()
10: {
11: Console.WriteLine("woof woof!");
12: }
13: }
14:
15: [ResolvesDependency(typeof(AnimalType), (int)AnimalType.Cat)]
16: public class Cat : IAnimal
17: {
18: public void MakeSound()
19: {
20: Console.WriteLine("meow!");
21: }
22: }
If you run the Test, a 'QueryablePropertyNotFoundException' will be thrown, which is correct, if you take a look at the Exception message, it is self-explanatory:
Expected queryable property of type [AnimalType] in class [AnimalCage]
We'll see what is missing in the next tutorial.
So far, we have told DependencyResolver that Cat and Dog services might be used to resolve an IAnimal dependency, but not how to determine which one should be used, so, based on the error message in Tutorial #2a, let's define the following property in our AnimalCage class:
26: [QueryableByDependencyResolver]
27: public AnimalType AnimalType { get; set; }
Notice the attribute above the property definition, it is telling DependencyResolver "whenever you need to know the current AnimalType, then look at the following property". Now that all of our bindings are complete you should be able to run the test:
49: animalCage.AnimalType = AnimalType.Dog;
50: dependencyResolver.ResolveDependencies(animalCage);
51: Assert.IsTrue(animalCage.Animal is Dog);
52: animalCage.GreetAnimal();
54: animalCage.AnimalType = AnimalType.Cat;
55: dependencyResolver.ResolveDependencies(animalCage);
56: Assert.IsTrue(animalCage.Animal is Cat);
57: animalCage.GreetAnimal();
It is important to be aware that each AnimalType can be binded to one and only one class. In this tutorial a new animal is being introduced (a duck), notice it is being binded to the wrong AnimalType:
24: // will produce an error, AnimalType.Cat is already in use by 'Cat' class
25: [ResolvesDependency(typeof(AnimalType), (int)AnimalType.Cat)]
26: public class Duck : IAnimal
27: {
28: public void MakeSound()
29: {
30: Console.WriteLine("quack quack!");
31: }
32: }
Since we are violating the rule described above, an exception of type 'ConfigurationAlreadyInUseException ' is expected:
56: try
57: {
58: var dependencyResolver = new Leos.DependencyResolver.DependencyResolver("DependencyResolverTests_Tutorial02c", new Logger());
59: Assert.Fail("Expected exception of type 'ConfigurationAlreadyInUseException'");
60: }
61: catch(ConfigurationAlreadyInUseException ex)
62: {
63: Trace.WriteLine(ex.Message);
64: }
65: // we don't even need to test anything else, DependencyResolver must crash when populating the DependencyInfo list
This tutorial shows that DependencyResolver is capable of resolving nested dependencies, notice that Ninja depends on IWeapon:
24: [ResolvesDependency]
25: class Ninja : IPlayer
26: {
27: [AutoResolved]
28: public IWeapon Weapon { get; set; }
29:
30: public void HitEnemy()
31: {
32: Console.WriteLine($"Damage taken by the enemy using [{Weapon.GetType().Name}]: {Weapon.GetHitDamage()}");
33: }
34: }
And at the same time, the Game class depends on an IPlayer instance:
36: class Game
37: {
38: [AutoResolved]
39: public IPlayer Player { get; set; }
40: }
Run the test and you will confirm that nested dependencies were resolved as follows:
46: var game = new Game();
47: dependencyResolver.ResolveDependencies(game, recursive: true);
48:
49: Assert.IsTrue(game.Player is Ninja);
50: Assert.IsTrue(game.Player.Weapon is Sword);
51:
52: game.Player.HitEnemy();
53: }
54: }
If for some reason you cannot or do not want to make use of automatic binding, you can still do manual bindings:
1: public class Tutorial04a
2: {
3: public class Dog : IAnimal
4: {
5: public void MakeSound()
6: {
7: Console.WriteLine("woof woof!");
8: }
9: }
10:
11: public class AnimalCage
12: {
13: public AnimalCage()
14: {
15: Animal = null;
16: }
17:
18: // even though we are using manual binding this time, we still need to tell DependecyResolver
19: // what dependencies need to be resolved
20: [AutoResolved]
21: public IAnimal Animal { get; set; }
22:
23: // since this property does not make use of the [AutoResolved] attribute, it should not be resolved
24: public IAnimal Animal2 { get; set; }
25:
26: public void GreetAnimal()
27: {
28: Animal.MakeSound();
29: Animal2.MakeSound();
30: }
31: }
32:
33: [TestMethod]
34: public void TestMethod4a()
35: {
36: var dependencyResolver = new Leos.DependencyResolver.DependencyResolver(new Logger());
37: dependencyResolver.Bind<IAnimal>().To<Dog>(); // manual binding
38: var animalCage = new AnimalCage();
39: dependencyResolver.ResolveDependencies(animalCage);
40:
41: Assert.IsNotNull(animalCage.Animal, "Animal1 dependency could not be resolved");
42: Assert.IsNull(animalCage.Animal2, "Animal2 should not have been resolved");
43: }
44: }
This tutorial shows how to do manual binding when a 'QueryableByDependencyResolver' property is needed:
1: public class Tutorial04b
2: {
3: public enum AnimalType { Dog = 0, Cat = 1, Rabbit = 2 };
4:
5: public class Dog : IAnimal
6: {
7: public void MakeSound()
8: {
9: Console.WriteLine("woof woof!");
10: }
11: }
12:
13: public class Cat : IAnimal
14: {
15: public void MakeSound()
16: {
17: Console.WriteLine("meow!");
18: }
19: }
20:
21: public class AnimalCage
22: {
23: [QueryableByDependencyResolver]
24: public AnimalType AnimalType { get; set; }
25:
26: public AnimalCage()
27: {
28: Animal = null;
29: }
30:
31: [AutoResolved]
32: public IAnimal Animal { get; set; }
33:
34: public void GreetAnimal()
35: {
36: Animal.MakeSound();
37: }
38: }
39:
40: [TestMethod]
41: public void TestMethod4b()
42: {
43: var dependencyResolver = new Leos.DependencyResolver.DependencyResolver(new Logger());
44:
45: dependencyResolver.Bind<IAnimal>().To<Dog>().When<AnimalType>().IsEqualTo(AnimalType.Dog);
46: dependencyResolver.Bind<IAnimal>().To<Cat>().When<AnimalType>().IsEqualTo(AnimalType.Cat);
47:
48: var animalCage = new AnimalCage();
49:
50: animalCage.AnimalType = AnimalType.Dog;
51: dependencyResolver.ResolveDependencies(animalCage);
52: Assert.IsTrue(animalCage.Animal is Dog);
53: animalCage.GreetAnimal();
54:
55: animalCage.AnimalType = AnimalType.Cat;
56: dependencyResolver.ResolveDependencies(animalCage);
57: Assert.IsTrue(animalCage.Animal is Cat);
58: animalCage.GreetAnimal();
59:
60: animalCage.AnimalType = AnimalType.Rabbit;
61:
62: try
63: {
64: dependencyResolver.ResolveDependencies(animalCage);
65: }
66: catch (ServiceNotFoundException)
67: {
68: // correct! There is no binding for IAnimal when 'AnimalType = Rabbit'
69: }
70: }
71: }
Notice at the end a 'ServiceNotFoundException' is thrown since we never manually binded the Rabbit AnimalType to any class.
DependencyResolver's code is hosted on GitHub:
https://github.com/leopoldolomas/DependencyResolver
If you would like to contribute to the project or
you have any comment/questions, here is my contact information:
Leopoldo Lomas Flores
leopoldolomas@gmail.com
Torreon, Coahuila. MEXICO.