Comparing .Net IoC containers, part six: Spring.Net

Tags: IocContainer, IocComparison, DependencyInjection, DI, IoC, code, spring, spring.net

Spring.NET is a port of a popular Java framework called Spring. Spring is very popular in the Java world, and it covers a lot of things, including IoC. Since I am comparing IoC containers in this article, I will address only the IoC aspects of the spring.Net port.

Spring has been quite influential. But the first iteration of an new idea is not always the final form. I have referred to Spring.Net as "transitional fossil", a prototype for later IoC containers. Despite its significance in the short history of IoC, I have no desire to use it in production code, and I have found it by far the hardest to get working in these tests.

Spring, by default, keeps config in XML the app.config file. This is more error-prone than code, and configurable in different ways: It allows class changes after the application is built, but does not allow two tests side-by side to use different configurations. These tests depend on doing that, which proved to be a major roadblock.

Here's the app.config required for the first test:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <sectionGroup name="spring">
      <section name="context" 
  type="Spring.Context.Support.ContextHandler, Spring.Core"/>
      <section name="objects" 
  type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
    </sectionGroup>
  </configSections>
  <spring>
    <context>
      <resource uri="config://spring/objects"/>
    </context>
    <objects xmlns="http://www.springframework.net">
      <object name="SweetShop" 
  type="IoCComparison.SweetShop, IoCComparison" 
  autowire="constructor" singleton="false" />
      <object name="SweetVendingMachine" 
  type="IoCComparison.SweetVendingMachine, IoCComparison" 
  autowire="constructor" singleton="false" />
      <object name="IJellybeanDispenser" 
  type="IoCComparison.VanillaJellybeanDispenser, IoCComparison" 
  autowire="constructor" singleton="false" />
    </objects>
  </spring>
</configuration>

And here's the code for the first test:

[Test]
public void CanMakeSweetShopWithVanillaJellybeans()
{
  IApplicationContext ctx = ContextRegistry.GetContext();
  SweetShop sweetShop = (SweetShop)ctx.GetObject("SweetShop");
  Assert.AreEqual(Jellybean.Vanilla, sweetShop.sweetShop.DispenseJellyBean());
}

And then ... then you're stuck. The config file is fixed for all tests in the same test project. No further tests with different configurations can pass. 

Most other IoC containers allow configuration in XML, but none other make it the default way of working.  I have not demonstrated this way of working with other containers, since it just isn't important to me. Yet all the spring introduction documentation emphasises this way. Putting part of your config in XML can be useful - it's a way of allowing a deployed application to be changed by changing the config, for instance deploying the same codebase for multiple clients who will be using different paths through the code. But it isn't the usual case.

The other tests can be done in Spring (there is good documentation on how to do constructor parameters and factory methods via config) but since this is all done in the app.config, it cannot be done at the same time as any of the other tests.

Note also how objects are accessed by name, not type, and then cast to the appropriate type, which I don't find very satisfactory. I could make a wrapper method that uses a generic and casts to that, but for simplicity's sake I have not included that in this demo. The name is not optional in spring. Other IoC containers generally allow an instance to be retrieved by an optional name in addition to the type. But unlike all the others, spring insists on it. The default model seems to be to retrieve a weakly typed object by name, not retrieving a strongly typed object by type.

Spring.Net without app.config

A while later, I had another go at spring, using a way of doing configuration in code that Marko Lahma showed me over on Stackoverflow. You need a different class of container, a GenericApplicationContext. The code used to configure it was quite verbose, so first I made some wrapper/helper code to make registering a type or a singleton more straightforward.

Note that Interface types like IJellybeanDispenser are not mentioned. The concrete type is automatically assumed to be an implementation for the interfaces that it has.

I got through the simplest type registration scenarios, but I didn't have time to do factory methods, contructor params  in this mode. It's not as well-documented as the configuration in the app.config file.

The way that the actual container is a different type when reading from code not config means that it's going to be harder to mix the two sources of configuration. In my opinion, even in situations when some types must be configured from App.config, configuration in code is prefered and should be used for the majority of types.


There is are two addon project called Recoil and CodeConfig  that aims to give registation code in spring a fluent interface.

But the basic problem with Spring.Net is that, although with a lot of wrapper code, addons and other hackery, you can probably turn it into a decent IoC container, why would you bother? There are first rate IoC containers ready to use out of the box.

The helper code

namespace IoCComparison
{
    using System;
    using Spring.Context.Support;
    using Spring.Objects.Factory.Config;
    using Spring.Objects.Factory.Support;

    public static class SpringHelper
    {
        public static void RegisterType<T>(this GenericApplicationContext context, string name)
        {
            context.RegisterType(name, typeof(T));
        }

        public static void RegisterSingleton<T>(this GenericApplicationContext context, string name)
        {
            context.RegisterSingleton(name, typeof(T));
        }

        public static void RegisterType(this GenericApplicationContext context, string name, Type type)
        {
            IObjectDefinitionFactory objectDefinitionFactory = new DefaultObjectDefinitionFactory();
            ObjectDefinitionBuilder builder = ObjectDefinitionBuilder.RootObjectDefinition(objectDefinitionFactory, type);
            builder.SetAutowireMode(AutoWiringMode.AutoDetect);
            builder.SetSingleton(false);

            context.RegisterObjectDefinition(name, builder.ObjectDefinition);
        }

        public static void RegisterSingleton(this GenericApplicationContext context, string name, Type type)
        {
            IObjectDefinitionFactory objectDefinitionFactory = new DefaultObjectDefinitionFactory();
            ObjectDefinitionBuilder builder = ObjectDefinitionBuilder.RootObjectDefinition(objectDefinitionFactory, type);
            builder.SetAutowireMode(AutoWiringMode.AutoDetect);
            builder.SetSingleton(true);

            context.RegisterObjectDefinition(name, builder.ObjectDefinition);
        }
    }
}

The test code

The test code is maintained here in a github repository.

namespace IoCComparison
{
    using Spring.Context;
    using Spring.Context.Support;
    using NUnit.Framework;

    /// <summary>
    /// Spring has been frustrating to set up 
    /// config file is undocumented and error-prone
    /// </summary>
    [TestFixture]
    public class SpringNoConfigTest
    {
        [Test]
        public void CanMakeSweetShopWithVanillaJellybeans()
        {
            GenericApplicationContext container = new GenericApplicationContext();
            container.RegisterType<SweetShop>("SweetShop");
            container.RegisterType<SweetVendingMachine>("SweetVendingMachine");
            container.RegisterType<VanillaJellybeanDispenser>("VanillaJellybeanDispenser");

            SweetShop sweetShop = (SweetShop)container.GetObject("SweetShop");

            Assert.AreEqual(Jellybean.Vanilla, sweetShop.DispenseJellyBean());
        }

        [Test]
        public void CanMakeSweetShopWithStrawberryJellybeans()
        {
            GenericApplicationContext container = new GenericApplicationContext();
            container.RegisterType<SweetShop>("SweetShop");
            container.RegisterType<SweetVendingMachine>("SweetVendingMachine");
            container.RegisterType<StrawberryJellybeanDispenser>("StrawberryJellybeanDispenser");
            SweetShop sweetShop = (SweetShop)container.GetObject("SweetShop");

            Assert.AreEqual(Jellybean.Strawberry, sweetShop.DispenseJellyBean());
        }

        [Test]
        public void JellybeanDispenserHasNewInstanceEachTime()
        {
            GenericApplicationContext container = new GenericApplicationContext();
            container.RegisterType<SweetShop>("SweetShop");
            container.RegisterType<SweetVendingMachine>("SweetVendingMachine");
            container.RegisterType<VanillaJellybeanDispenser>("VanillaJellybeanDispenser");

            SweetShop sweetShop = (SweetShop)container.GetObject("SweetShop");
            SweetShop sweetShop2 = (SweetShop)container.GetObject("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()
        {
            GenericApplicationContext container = new GenericApplicationContext();
            container.RegisterType<SweetShop>("SweetShop");
            container.RegisterType<SweetVendingMachine>("SweetVendingMachine");
            container.RegisterSingleton<VanillaJellybeanDispenser>("VanillaJellybeanDispenser");

            SweetShop sweetShop = (SweetShop)container.GetObject("SweetShop");
            SweetShop sweetShop2 = (SweetShop)container.GetObject("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()
        {
            GenericApplicationContext container = new GenericApplicationContext();
            container.RegisterType<AniseedSweetShop>("SweetShop");

            SweetShop sweetShop = (SweetShop)container.GetObject("SweetShop");

            Assert.AreEqual(Jellybean.Aniseed, sweetShop.DispenseJellyBean());
        }

        [Test]
        [Ignore("Spring is inflexible")]
        public void CanUseAnyJellybeanDispenser()
        {
            IApplicationContext ctx = ContextRegistry.GetContext();
            SweetShop sweetShop = (SweetShop)ctx.GetObject("SweetShop");

            Assert.AreEqual(Jellybean.Lemon, sweetShop.DispenseJellyBean());
        }


        [Test]
        [Ignore("Spring is inflexible")]
        public void CanUseConstructedObject()
        {
            IApplicationContext ctx = ContextRegistry.GetContext();
            SweetShop sweetShop = (SweetShop)ctx.GetObject("SweetShop");

            Assert.AreEqual(Jellybean.Cocoa, sweetShop.DispenseJellyBean());
        }

        [Test]
        [Ignore("Spring is inflexible")]
        public void CanUseFactoryMethod()
        {
            IApplicationContext ctx = ContextRegistry.GetContext();
            SweetShop sweetShop = (SweetShop)ctx.GetObject("SweetShop");

            Assert.AreEqual(Jellybean.Orange, sweetShop.DispenseJellyBean());
        }
    }
}
Add a Comment