Sunday, May 23, 2010

A Simple Inversion of Control (IoC) Container in F#

In this post I will show how to build a simple Inversion of Control (IoC) container in F#.  If you are new to IoC containers I would recommend getting acquainted by reading http://martinfowler.com/articles/injection.html and http://msdn.microsoft.com/en-us/library/aa973811.aspx.

The Code:
namespace FSharpIoC

open System
open System.Collections.Generic
open System.Reflection

module FSharpIoCModule = 
    let rec Resolve requestedType (typeContainer:Dictionary<Type,Type>) =
        let newType = typeContainer.[requestedType]
        let constructors = newType.GetConstructors() 
                           |> Array.sortBy (fun c -> c.GetParameters().Length) 
                           |> Array.rev
        let theConstructor = constructors.[0]
        match theConstructor.GetParameters() with
        | cstorParams when cstorParams.Length = 0 -> Activator.CreateInstance(newType)
        | cstorParams -> cstorParams 
                         |> Seq.map(fun (paramInfo:ParameterInfo) -> 
                                (Resolve paramInfo.ParameterType typeContainer)) 
                         |> Seq.toArray 
                         |> theConstructor.Invoke

type Container =
    val typeContainer : Dictionary<Type, Type>
    new () = {typeContainer = new Dictionary<Type, Type>()}
    member x.Register<'a,'b> () =        
        x.typeContainer.Add(typeof<'a>, typeof<'b>)
    member x.Resolve(requestedType) =
        FSharpIoCModule.Resolve requestedType x.typeContainer
    member x.Resolve<'a> () =
        FSharpIoCModule.Resolve typeof<'a> x.typeContainer :?> 'a
Using Our IoC Container From F#:

The code below is not exactly a "real world" example, but it does show how our IoC container could be used in an F# application:
module FSharpIoCExample

open System
open FSharpIoC

type Person =
    { FirstName : string
      LastName : string}

type IPersonFactory =
    abstract CreatePerson : unit -> Person

type PersonFactory() = 
    interface IPersonFactory with
        member x.CreatePerson () = 
            {FirstName = "TestFirst"; LastName = "TestLast"}

type PersonService = 
    val personFactory : IPersonFactory
    new (personFactory) = {personFactory = personFactory}
    member x.GetPerson () = 
        x.personFactory.CreatePerson()

let iocContainer = new Container()
do iocContainer.Register<IPersonFactory, PersonFactory>()
do iocContainer.Register<PersonService, PersonService>()
let personService = iocContainer.Resolve<PersonService>()
let person = personService.GetPerson()
do Console.WriteLine("The person's name is {0} {1}", person.FirstName, person.LastName)
do Console.ReadLine()
Using Our IoC Container From C#:

Here is the same example in C#:
using System;
using FSharpIoC;

namespace CSharpIoCExample
{
    class Program
    {
        static void Main(string[] args)
        {
            var iocContainer = new Container();
            iocContainer.Register<IPersonFactory, PersonFactory>();
            iocContainer.Register<PersonService, PersonService>();
            var personService = iocContainer.Resolve<PersonService>();
            var person = personService.GetPerson();
            Console.WriteLine("The person's name is {0} {1}", person.FirstName, person.LastName);
            Console.ReadLine();
        }
    }

    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

    public interface IPersonFactory
    {
        Person CreatePerson();
    }

    public class PersonFactory : IPersonFactory
    {
        public Person CreatePerson()
        {
            return new Person { FirstName = "TestFirst", LastName = "TestLast" };
        }
    }

    public class PersonService
    {
        protected readonly IPersonFactory _personFactory;
        public PersonService(IPersonFactory personFactory)
        {
            _personFactory = personFactory;
        }

        public Person GetPerson()
        {
            return _personFactory.CreatePerson();
        }
    }
}
Conclusion

As you can see, it doesn't take much to build a slim featured IoC container in F#. You can find the full source at http://github.com/dmohl/FSharpIoC.

3 comments:

  1. Great post - it's really helpful to have posts connecting terminology and code like this.

    BTW why not use implicit construction for PersonService?

    type PersonService(personFactory: IPersonFactory) =
    member x.GetPerson () = personFactory.CreatePerson()

    ReplyDelete
  2. That's a great point! Implicit construction is a more succinct way to accomplish this functionality.

    ReplyDelete
  3. That's a great point! Implicit construction is a more succinct way to accomplish this functionality.

    ReplyDelete