5.26.2010

Structure Map 2.6 Constructor Arguments

Constructor Injection

When we use an IoC tool the most basic functionality we expect is to have constructor injection. That means if we have a type we are resolving and one of the constructor’s parameters is also a type, the container will resolve that type as well.

And turtles all the way down…

If we have two types(Foo : IFoo and Bar : IBar) and in Bar’s constructor it has an IFoo parameter, we should expect the IFoo parameter to be resolved to an instance of Foo.

class Bar : IBar
{
    public Bar(IFoo foo)
    {
        Foo = foo;
    }

    public IFoo Foo { get; set; }
}

Our container is setup like :

_container = new Container();

_container.Configure(x =>
                         {
                             x.For<IFoo>().Use<Foo>();
                             x.For<IBar>().Use<Bar>();
                         });

We then expect :

[Test]
public void IBar_should_resolve_to_Bar()
{
    var bar = _container.GetInstance<IBar>();

    bar.ShouldBeType<Bar>();
}

[Test]
public void IBars_IFoo_dependency_should_resolve_to_Foo()
{
    var bar = _container.GetInstance<IBar>();

    bar.Foo.ShouldBeType<Foo>();
}

Constructor Arguments

Sometimes you need to specify the exact argument for a given type when it is constructed. Structure Map allows you to provide specific values for resolving a constructors argument.

For example, Ryan was asking if there is a method with SM to resolve an “id” value from an HttpContext. I know SM has some fancy session management lifecycle features built in, but since I don’t live in the Web world I have never explored what can and can not be done with those features.

What I do know is that I can specify where my “id” parameter comes from.

Let’s look at this simple example.

In my scenario I have a Foo class.

class Foo : IFoo
{
    public Foo(int id, IBar bar)
    {
        Id = id;
        Bar = bar;
    }

    public int Id { get; set; }
    public IBar Bar { get; set; }
}

The id that is passed into Foo is providing by some mysterious X. In my case my X is an IIdProvider.

class IdProvider : IIdProvider
{
    public int GetId()
    {
        return 1;
    }
}

Now I want to tell SM that when it resolves an IFoo, I want it to use the IIdProvider to get the “id”.

x.For<IFoo>().Use<Foo>()
    .Ctor<int>("id")
    .Is(c => c.GetInstance<IIdProvider>().GetId());

All together the configuration is:

_container = new Container();
_container.Configure(x =>
                         {
                             x.For<IIdProvider>().Use<IdProvider>();
                             x.For<IBar>().Use<Bar>();
                             x.For<IFoo>().Use<Foo>()
                                 .Ctor<int>("id")
                                 .Is(c => c.GetInstance<IIdProvider>().GetId());
                         });

And my tests to verify it works:

[Test]
public void should_resolve_Bar()
{
    var foo = _container.GetInstance<IFoo>();

    foo.Bar.ShouldBeType<Bar>();
}

[Test]
public void should_resolve_id()
{
    var foo = _container.GetInstance<IFoo>();

    foo.Id.ShouldBe(1);
}

Lazy Constructor Arguments

Then Ryan tells me, he would really like to be lazy because he might not need the id for all requests. There are some Lazy features added to SM 2.6 but let’s just use what we already know to solve this problem.

Instead of having an int Id parameter, let’s have a Func<int> getId parameter.

Foo changes to :

class Foo : IFoo
{
    public Foo(Func<int> getId, IBar bar)
    {
        GetId = getId;
        Bar = bar;
    }

    public Func<int> GetId { get; set; }
    public IBar Bar { get; set; }
}

Now our constructor configuration changes to :

x.For<IFoo>().Use<Foo>()
    .Ctor<Func<int>>("getId")
    .Is(c => c.GetInstance<IIdProvider>().GetId);

And our tests become :

[Test]
public void should_resolve_Bar()
{
    var foo = _container.GetInstance<IFoo>();

    foo.Bar.ShouldBeType<Bar>();
}

[Test]
public void should_resolve_id()
{
    var foo = _container.GetInstance<IFoo>();

    foo.GetId().ShouldBe(1);
}

Yes it is a poor man’s lazy instantiation. But in many ways its the clearest way to have the functionality.

You can find all this code in my Git Hub Learning Repository and the Learning Structure Map solution : http://github.com/RookieOne/Learning

1 comment:

  1. Thanks for this info. If only the StructureMap project itself had decent documentation like this. The site is so filled with ancient and deprecated interfaces. It totally intimidates and misleads newbs.

    ReplyDelete