Back in February, I blogged about Needle, a dependency injection container that I was developing. In that post, I listed some of the most relevant features Needle has, one of them being its Fluent API. I thought it would be a would idea to talk about Needle again, but this time sharing some code and ideas behind it.
First things first, why do I want fluency?
This is of course a matter of appreciation, but I personally relate fluency to both ease of use and readability. Interestingly enough one of them is important before writing the code, and the other one after doing so. The idea is that a fluent API will give you a small set of options when performing a particular operation, each of them will be presented at the correct moment, and that once the code has been written, you can explain it to your non-tech grandma.
Our fluent scenario, mapping types
A really common operation that needs to be performed when working with DI containers is mapping types, which involves configuring the container to determine the type you want to get when requesting another type. This is commonly used by mapping an interface to a concrete implementation.
Let’s say we have an IForceEnlightened interface, and a Jedi class which implements that interface. If we wanted to get a Jedi whenever we request an instance of IForceEnlightened from the container, we can write the following code:
INeedleContainer needleContainer = new NeedleContainer(); needleContainer .Map<IForceEnlightened>() // when we request an IForceEnlightened .To<Jedi>() // provide a Jedi .Commit(); // save the mapping
One really important thing about the fluent API is that, in every step of the way, IntelliSense only shows us the relevant members:
The Commit() method call might have caught your eye in the previous code sample. This call is necessary, because Needle uses a transactional operation model. When performing a configuration operation with Needle, a committable object is created, which you must commit back to the container for the operation to take place.
This can also be useful in situations where you need to perform a certain configuration in a component, but want to wait until that configuration takes place. In this cases, keeping a reference to the ICommitable object until you want the configuration to take place does the trick.
Each thing by its own name
Let’s say we have another scenario, in which we could need either a Jedi or a Sith when requesting a force enlightened subject. In this case, the following code would do the trick:
needleContainer .Map<IForceEnlightened>() .To<Jedi>() .WithId("Jedi") .Commit(); needleContainer .Map<IForceEnlightened>() .To<Sith>() .WithId("Sith") .Commit();
In case we want to get the same Jedi instance each time we request a force enlightened subject, we can change the default lifetime registration from Transient (a different instance each time we request an IForceEnlightened subject) to Singleton (returns the same instance every time).
needleContainer .Map<IForceEnlightened>() .To<Jedi>() .UsingLifetime(RegistrationLifetime.Singleton) .Commit();
Singleton registrations are commonly used with services in most applications.
Putting it all together
Of course, we can both name and determine the scope of a registration. If we want fast and simple access to Master Yoda (and who doesn’t), we can name the mapping and set its lifetime to Singleton:
needleContainer .Map<IForceEnlightened>() .To<Jedi>() .WithId("Yoda") .UsingLifetime(RegistrationLifetime.Singleton) .Commit();
I hope you found this post fun, and that you try out Needle and its fluent API sometime. You can download Needle from here or through Nuget:
PM> Install-Package NeedleContainer