views:

390

answers:

2

An advantage of an IoC container is that you can swap in a mock service at the bottom of your object graph. However this seems much harder to do in Spring.Net than in other IoC Containers. Here's some code that does it in Unity and has Spring.Net code;

namespace IocSpringDemo
{
    using Microsoft.Practices.Unity;
    using NUnit.Framework;

    using Spring.Context;
    using Spring.Context.Support;

    public interface ISomeService
    {
        string DoSomething();
    }

    public class ServiceImplementationA : ISomeService
    {
        public string DoSomething()
        {
            return "Hello A";
        }
    }

    public class ServiceImplementationB : ISomeService
    {
        public string DoSomething()
        {
            return "Hello B";
        }
    }

    public class RootObject
    {
        public ISomeService SomeService { get; private set; }

        public RootObject(ISomeService service)
        {
            SomeService = service;
        }
    }

    [TestFixture]
    public class UnityAndSpringDemo
    {
        [Test]
        public void UnityResolveA()
        {
            UnityContainer container = new UnityContainer();
            container.RegisterType<ISomeService, ServiceImplementationA>();
            RootObject rootObject = container.Resolve<RootObject>();
            Assert.AreEqual("Hello A", rootObject.SomeService.DoSomething());
        }

        [Test]
        public void UnityResolveB()
        {
            UnityContainer container = new UnityContainer();
            container.RegisterType<ISomeService, ServiceImplementationB>();
            RootObject rootObject = container.Resolve<RootObject>();
            Assert.AreEqual("Hello B", rootObject.SomeService.DoSomething());
        }

        [Test]
        public void SpringResolveA()
        {
            IApplicationContext container = ContextRegistry.GetContext();
            RootObject rootObject = (RootObject)container.GetObject("RootObject");
            Assert.AreEqual("Hello A", rootObject.SomeService.DoSomething());
        }

        [Test]
        public void SpringResolveB()
        {
            // does not work - what to do to make this pass?
            IApplicationContext container = ContextRegistry.GetContext();
            RootObject rootObject = (RootObject)container.GetObject("RootObject");
            Assert.AreEqual("Hello B", rootObject.SomeService.DoSomething());
        }
    }
}

For the benefit of Spring, the following needed to be in the App.config file. Clearly this only serves the first spring test, and not the second. Can you put multiple spring configurations in the config file? If so, what is the syntax and how do you access them? Or is there another way to do this?

  <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"&gt;
      <object name="RootObject" type="IocSpringDemo.RootObject, IocDemo" autowire="constructor" />
      <object name="service" type="IocSpringDemo.ServiceImplementationA, IocDemo" autowire="constructor" />
    </objects>
  </spring>

Update

Here is a partial answer based at code at the links that Marko Lahma gave to Mark Pollack's blog. I have the above tests passing, with the following code:

public static class SpringHelper
{
    public static T Resolve<T>(this IApplicationContext context, string name)
    {
        return (T)context.GetObject(name);
    }

    public static void RegisterType<T>(this GenericApplicationContext context, string name)
    {
        context.RegisterType(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);

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

...

    [Test]
    public void SpringResolveA()
    {
        GenericApplicationContext container = new GenericApplicationContext();
        container.RegisterType<RootObject>("RootObject");
        container.RegisterType<ServiceImplementationA>("service");

        RootObject rootObject = container.Resolve<RootObject>("RootObject");
        Assert.AreEqual("Hello A", rootObject.SomeService.DoSomething());
    }

    [Test]
    public void SpringResolveB()
    {
        GenericApplicationContext container = new GenericApplicationContext();
        container.RegisterType<RootObject>("RootObject");
        container.RegisterType<ServiceImplementationB>("service");

        RootObject rootObject = container.Resolve<RootObject>("RootObject");
        Assert.AreEqual("Hello B", rootObject.SomeService.DoSomething());
    }

This raises a few questions to me:

  • I want to integrate this technique into existing code that uses the usual container. Why do I have to use a different container type, GenericApplicationContext in this case? What if I want to read data into this object from the existing spring config in app.config or web.config? Would it work as the usual context? Could I then write data over these registrations with code?

  • How can I specify that ISomeService is to be created as a singleton? I don't mean supply a singleton instance to the container, but the container to create the instance, resolving its constructor, and use it when that type is needed.

  • how can I do the equivalent of container.RegisterType<ISomeService, ServiceImplementationA>(); ? I want to register type mappings to use in all cases where that type is needed by a constructor.

  • What exactly does container.RegisterType<ServiceImplementationA>("service"); do? It seems to register ServiceImplementationA as the implementation of ISomeService but ISomeServiceis never mentioned, so there could be ambiguity. e.g. what if ServiceImplementationA implemented more than one interface.

  • What is the string name given to the registration for? It won't work with en empty string, but it doesn't seem to matter what it is.

Am I trying to use spring in a way that it just does not work? I'm trying to use it like other IoC containers, but it's not quite working.

+1  A: 

That's a bit apples and oranges comparison as the unit test uses code configuration for Unity and XML (app.config) configuration for Spring.NET.

If you go the XML route, then you can either comment out old implementation A and define the B implementation as the one to use - that what's configuration is all about right? Other option is to have dedicated XML files for each scenario (configuration setup) and include them via context's resource definitions (you have inline resource now). Other options include file system and assembly, see the web configuration section in Spring.NET's manual for a nice example.

If you go the code configuration route I would suggest to check Spring.NET Recoil and upcoming CodeConfig.

Marko Lahma
With regards the "apples and oranges comparison", but I tried to use each tool according to its usual practice and the available examples. So it uses config in code for unity and config in app.config for spring. Do unity and spring in fact cover the same scenarios? Or is that the "apples vs. oranges" ?Suggesting that the A config be commented out for B to work is not a good idea. This e.g. simulates test cases with mocks. Different mocks may be required per test, and all tests have to pass in one go. This scenario is key to us an many others.
Anthony
I was lead to believe that spring could not do config in code at all. Thanks for the links that say otherwise. Are these add-ons that do this mature? I’d also like to see a working example for the configuration setup method that you mention. Though it seemed possible, the documentation that I found is not clear on exactly what to do here. It's a one-liner in most other IoC containers, what's it like in Spring?
Anthony
This authoritative answer: http://stackoverflow.com/questions/411660/enterprise-library-unity-vs-other-ioc-containers/423168#423168 says that Spring.Net is config in XML only.
Anthony
Yes, XML is the usual way to configure Spring.NET. There is always to underlying configuration meta model that consists of normal .NET classes. Mark Pollack has a nice blog post about it here: http://blog.springsource.com/2008/01/04/spring-net-11-and-container-configuration/ .Spring.NET Recoil is 3rd party work and is an extension on top of configuration model (just like XML). CodeConfig is work in progress and made by Spring.NET core developer(s).
Marko Lahma
Used the "apples and oranges" to indicate the basic problem with the different approaches. Code configuration is easier for test cases and XML suits better when there's quite static model (or XML scenario files that can be combined as-is without modification).
Marko Lahma
I have tried out some code, and added it to the original question.
Anthony
+1  A: 

Adding as new answer trying to address the open points...

I want to integrate this technique into existing code that uses the usual container. Why do I have to use a different container type, GenericApplicationContext in this case? What if I want to read data into this object from the existing spring config in app.config or web.config? Would it work as the usual context? Could I then write data over these registrations with code?

Spring has concrete application context implementations for different kind of initialization tactics. The most common ones to use are GenericApplicationContext (manual), XmlApplicationContext (XML files) and WebApplicationContext (very much like XmlApplicationContext but tailored for web use). They all implement common interface: IApplicationContext which is the preferred way to access these containers.

Unfortonately altering registrations with code usually means that you need to use the specific sub-class directly. With GenericApplicationContext and StaticApplicationContext this is quite natural but XmlApplicationContext is usually considered to be XML only and this ways "fixed" to XML definition.

How can I specify that ISomeService is to be created as a singleton? I don't mean supply a singleton instance to the container, but the container to create the instance, resolving its constructor, and use it when that type is needed.

Your SpringHelper does just that, by default all objects in Spring are singletons. You could alter this behavior by calling ObjectDefinitionBuilder's SetSingleton method with false.

how can I do the equivalent of container.RegisterType(); ? I want to register type mappings to use in all cases where that type is needed by a constructor.

Spring uses object names (ids) to distinct between different implementations. So if you want to get specific type to serve a specific instance in case that there are many alternatives you should refer to this specific instance by name. If you are using autowiring and your object has dependency to interface ISomeService and there's only one object registered that implements it, the autowiring can set it without ambiguity.

What exactly does container.RegisterType("service"); do? It seems to register ServiceImplementationA as the implementation of ISomeService but ISomeServiceis never mentioned, so there could be ambiguity. e.g. what if ServiceImplementationA implemented more than one interface.

Continuing from previous answer, this registers singleton of type ServiceImplementationA with name "service". This object comes autowiring candidate with all it's implemented interfaces (and with it's concrete type of course).

What is the string name given to the registration for? It won't work with en empty string, but it doesn't seem to matter what it is.

It matters a great deal as explained earlier. The name is unique id within that context (parent context could have object with same name) and can be used to access specific object registrations. In short where other frameworks may associate a type as key to object registration, Spring uses name.

Marko Lahma
Mixing XML and code config is important, it's a pity it's not easy to do.What does the registration name match to? Suppose that I have two implementations of ISomeService registered - which one is plugged into an autowired constructor - is it the one where the registration name matches the param name?
Anthony
Related, you can do new GenericApplicationContext(IApplicationContext parent). Would this allow you to put a XmlApplicationContext in as the parent, and effectively resolve classes using both - code config if present, fall back to the parent XML config?
Anthony
Update: A simple test with " new GenericApplicationContext(ContextRegistry.GetContext()) " suggests that you cannot resolve classes using registrations in both. What is the parent context for then?
Anthony
Spring.NET has different autowiring modes, described here: http://www.springframework.net/doc/reference/html/objects.html#objects-factory-autowire . So 0 or more than 1 is error condition.
Marko Lahma
Parent context allows for example separation between framework level and modules level. You can host your common, shared service in root context and create sub contexts for something like plugins. Then plugins will find their dependencies from the container they are specified in or from the parent container. Child container definitions can "overwrite" parent's definitions by using their definitions closer visibility.
Marko Lahma
Maybe this warrants a new question now, but "find dependencies from the container they are specified in or from the parent container" is exactly what we want, but it doesn't work for me. It finds the dependencies in the parent container, but not the root.
Anthony
This makes it sound that you have three containers? make sure that they are all chained together.. You can search for objects from child towards root traversing all parents in chain.
Marko Lahma
Only 2 containers. If the root object that you are trying to resolve is not registered in the child container but is in the parent, it will not be found in the child container, even though the root object's dependencies are found in that way.
Anthony