IoC comparison: OpenRasta Internal Dependency Resolver
OpenRasta is an excellent .Net web framework written by Sebastien Lambla. OpenRasta is open source, is opinionated about REST, but is also highly flexible and configurable. So you can plug any Inversion of Control container into it, but it also contains a basic built-in IoC container that is used if no external container is supplied. Even though this container is not intended for use outside of OpenRasta, I decided to isolate it and put it through my IoC comparison tests.
Open Rasta refers to IoC containers as "dependency resolvers", which is a good description of what OpenRasta needs them to do - resolve references to dependant objects. The container is called the InternalDependencyResolver, but it is referenced via the interface IDependencyResolver. This is so that if you do supply another IoC container (e.g. Unity or Windsor) populated with your app's registrations, you just need a wrapper class that implements IDependencyResolver and translates to the syntax of your container. The IDependencyResolver interface is fairly minimal, but a set of Extension methods provide a richer interface of generics and overloads to it. See the code for these on GitHub. This has the benefit that these extension methods will provide that same richness to any new implementation of the interface.
Wrappers exist for the major .Net IoC containers, and it doesn't look that hard to implement another one for a container that provides the same set of internal operations in some manner.
The way that the OpenRasta internal dependency resolver works will hold no surprises to users of Windsor or Unity. This is to be expected, since it is designed to be compatible with wrappers around them.
Registration is done via overloads of container.AddDependency<T>() and resolution is done via container.Resolve<T>()
Objects are registered as singleton by default. Like Windsor but unlike Unity, types that have not been registered will not be resolved – an exception is thrown instead. This is probably a difference to be aware of when using your prefered IoC container with OpenRasta.
The container is mutable, since new registrations can be done on it at any time. Constructed instances can be registered into the container.
The container as it stands does not support any kind of factory function; however I took a look at the code and hacked this feature into place. This code is currently only here on a fork of OpenRasta on GitHub that I made for this purpose.
Another way of gaining advanced IoC container features would be to use your external IoC container of choice, configure it to your liking, and then wrap it up and give it to OpenRasta to use. The calls to Resolve<> will then use your configuration. OpenRasta also uses the AddDependency calls, so it doesn't look like you can use a completely immutable container inside it, but that can probably be worked around in the wrapper class. So the autofac wrapper is likely to have more overhead than the Unity or Windsor wrapper.
The test code is below, but is maintained in the repsitory on Github
namespace IoCComparison
{
using NUnit.Framework;
using OpenRasta.DI;
using System.Collections.Generic;
using System.Linq;
[TestFixture]
public class OpenRastaTest
{
[Test]
public void CanMakeSweetShopWithVanillaJellybeans()
{
IDependencyResolver container = new InternalDependencyResolver();
container.AddDependency<SweetShop>();
container.AddDependency<SweetVendingMachine>();
container.AddDependency<IJellybeanDispenser, VanillaJellybeanDispenser>();
SweetShop sweetShop = container.Resolve<SweetShop>();
Assert.AreEqual(Jellybean.Vanilla, sweetShop.DispenseJellyBean());
}
[Test]
public void CanMakeSweetShopWithStrawberryJellybeans()
{
IDependencyResolver container = new InternalDependencyResolver();
container.AddDependency<SweetShop>();
container.AddDependency<SweetVendingMachine>();
container.AddDependency<IJellybeanDispenser, StrawberryJellybeanDispenser>();
SweetShop sweetShop = container.Resolve<SweetShop>();
Assert.AreEqual(Jellybean.Strawberry, sweetShop.DispenseJellyBean());
}
[Test]
public void JellybeanDispenserHasNewInstanceEachTime()
{
IDependencyResolver container = new InternalDependencyResolver();
// default is " DependencyLifetime.Singleton" so specify transient objects
container.AddDependency<SweetShop>(DependencyLifetime.Transient);
container.AddDependency<SweetVendingMachine>(DependencyLifetime.Transient);
container.AddDependency<IJellybeanDispenser, VanillaJellybeanDispenser>(DependencyLifetime.Transient);
SweetShop sweetShop = container.Resolve<SweetShop>();
SweetShop sweetShop2 = container.Resolve<SweetShop>();
Assert.IsFalse(ReferenceEquals(sweetShop, sweetShop2), "Root objects are equal");
Assert.IsFalse(ReferenceEquals(sweetShop.SweetVendingMachine, sweetShop2.SweetVendingMachine), "Contained objects are equal");
Assert.IsFalse(ReferenceEquals(sweetShop.SweetVendingMachine.JellybeanDispenser, sweetShop2.SweetVendingMachine.JellybeanDispenser), "services are equal");
}
[Test]
public void CanMakeSingletonJellybeanDispenser()
{
IDependencyResolver container = new InternalDependencyResolver();
container.AddDependency<SweetShop>(DependencyLifetime.Transient);
container.AddDependency<SweetVendingMachine>(DependencyLifetime.Transient);
container.AddDependency<IJellybeanDispenser, StrawberryJellybeanDispenser>(DependencyLifetime.Singleton);
SweetShop sweetShop = container.Resolve<SweetShop>();
SweetShop sweetShop2 = container.Resolve<SweetShop>();
Assert.IsFalse(ReferenceEquals(sweetShop, sweetShop2), "Root objects are equal");
Assert.IsFalse(ReferenceEquals(sweetShop.SweetVendingMachine, sweetShop2.SweetVendingMachine), "Contained objects are equal");
// should be same service
Assert.IsTrue(ReferenceEquals(sweetShop.SweetVendingMachine.JellybeanDispenser, sweetShop2.SweetVendingMachine.JellybeanDispenser), "services are not equal");
}
[Test]
public void CanMakeAniseedRootObject()
{
IDependencyResolver container = new InternalDependencyResolver();
container.AddDependency<SweetShop, AniseedSweetShop>();
SweetShop sweetShop = container.Resolve<SweetShop>();
Assert.AreEqual(Jellybean.Aniseed, sweetShop.DispenseJellyBean());
}
[Test]
public void CanUseConstructedObject()
{
IDependencyResolver container = new InternalDependencyResolver();
container.AddDependency<SweetShop>(DependencyLifetime.Transient);
container.AddDependency<SweetVendingMachine>(DependencyLifetime.Transient);
container.AddDependencyInstance<IJellybeanDispenser>(new AnyJellybeanDispenser(Jellybean.Cocoa));
SweetShop sweetShop = container.Resolve<SweetShop>();
Assert.AreEqual(Jellybean.Cocoa, sweetShop.DispenseJellyBean());
}
/// <summary>
/// This works on my fork of the OpenRasta container code
/// </summary>
[Test]
public void CanUseObjectFactory()
{
IDependencyResolver container = new InternalDependencyResolver();
container.AddDependency<SweetShop>(DependencyLifetime.Transient);
container.AddDependency<SweetVendingMachine>(DependencyLifetime.Transient);
container.AddDependency<IJellybeanDispenser>(c => new AnyJellybeanDispenser(Jellybean.Orange));
SweetShop sweetShop = container.Resolve<SweetShop>();
Assert.AreEqual(Jellybean.Orange, sweetShop.DispenseJellyBean());
}
/// <summary>
/// This works on my fork of the OpenRasta container code
/// </summary>
[Test]
public void CanUseObjectFactoryWithSingleton()
{
IDependencyResolver container = new InternalDependencyResolver();
container.AddDependency<SweetShop>(DependencyLifetime.Transient);
container.AddDependency<SweetVendingMachine>(DependencyLifetime.Transient);
IJellybeanDispenser instance = new AnyJellybeanDispenser(Jellybean.Orange);
// The lambda captures the instance and uses it across multiple calls. It's a singleton
container.AddDependency<IJellybeanDispenser>(c => instance);
SweetShop sweetShop = container.Resolve<SweetShop>();
SweetShop sweetShop2 = container.Resolve<SweetShop>();
Assert.AreEqual(Jellybean.Orange, sweetShop.DispenseJellyBean());
Assert.AreEqual(Jellybean.Orange, sweetShop2.DispenseJellyBean());
Assert.IsFalse(ReferenceEquals(sweetShop, sweetShop2), "Root objects are equal");
Assert.IsFalse(ReferenceEquals(sweetShop.SweetVendingMachine, sweetShop2.SweetVendingMachine), "Contained objects are equal");
// should be same service
Assert.IsTrue(ReferenceEquals(sweetShop.SweetVendingMachine.JellybeanDispenser, sweetShop2.SweetVendingMachine.JellybeanDispenser), "services are not equal");
}
[Test]
public void CanRegisterMultipleDispensers()
{
IDependencyResolver container = new InternalDependencyResolver();
container.AddDependency<IJellybeanDispenser, VanillaJellybeanDispenser>();
container.AddDependency<IJellybeanDispenser, StrawberryJellybeanDispenser>();
IEnumerable<IJellybeanDispenser> dispensers = container.ResolveAll<IJellybeanDispenser>();
Assert.IsNotNull(dispensers);
Assert.AreEqual(2, dispensers.Count());
}
}
}