StructureMap: Creation as Transient (per request) not working
Asked Answered
M

3

5

I'm trying to solve a IoC problem, that seemed easy at first, but turned out to be a pain in the ass:-P

I have a heavy weight main class, which must be initialized only once, so it's marked as Singleton. However this class uses a subclass which must be created once for each request, so it's marked as Transient:

public class MyRegistry : Registry
{
    public MyRegistry()
    {
        For<IMainClass>()
            .Singleton()
            .Use(ctx => new MainClass(() => ctx.GetInstance<ISubClass>()));

        For<ISubClass>()
            .Transient()
            .Use(ctx => CreateNewInstanceOfSubClass());
    }

    private ISubClass CreateNewInstanceOfSubClass()
    {
        return new SubClass();
    }
}

public interface ISubClass
{ }

public class SubClass : ISubClass
{ }

public interface IMainClass
{ }

public class MainClass : IMainClass
{
    private readonly Func<ISubClass> _subClassProvider;

    public MainClass(Func<ISubClass> subClassProvider)
    {
        _subClassProvider = subClassProvider;
    }

    public void DoStuff()
    {
        var requestSpecificInstanceOfSubClass = _subClassProvider();

        // ...
    }
}

As you can see, I pass a lambda to the constructor of MainClass, which is used to get an instance of ISubClass. During debugging I could definitely see that ctx.GetInstance<ISubClass>() is executed each time MainClass needs a SubClass instance. But to my surprise, SubClass is only created once as a singleton, instead of being created for each request.

However, when I call container.GetInstance<ISubClass>() directly from somewhere within my code the behavior is exactly as desired. SubClass is created once and only once for each request.

I'm no quite sure, but I guess the problem comes from the context object that is passed to the lambda, which is the context of a singleton(?). But I really don't know how to get the desired behavior here!?

I hope you can help me with this one. Thanks for your answers.

Regards, Dante

Mouflon answered 27/3, 2016 at 11:41 Comment(2)
"I have a heavy weight main class" - I think your problem lies here. When using Dependency Injection, constructors should be simple. When does your MainClass need to be initialized? At application startup? At first usage?Katherine
Yes, MainClass must be initialized at application startup. Actually the class itself is not too complicated. But it creates and manages a TCP socket. So it's absolutely crucial that this class is only created once. I agree, that constructors must be as simple as possible. In this case I must find a way to create a new instance of SubClass for each request without creating new MainClass instances. So that's a bit tricky.Mouflon
K
1

It seems that you need to rework the design a little. To avoid having captive dependencies, you need to give the root object of the graph a shorter (or equal) lifetime than any of its dependencies.

One option would be to make a 3rd class to manage the interaction between MainClass and SubClass.

Interfaces

public interface IInteractionManager
{ 
    void DoStuff();
}

public interface IMainClass
{ 
    void DoStuff(ISubclass subclass);
}

public interface ISubClass
{ }

Classes

public class InteractionManager : IInteractionManager
{
    private readonly IMainClass mainClass;
    private readonly ISubClass subClass;

    public InteractionManager(
        IMainClass mainClass,
        ISubClass subClass)
    {
        this.mainClass = mainClass;
        this.subClass = subClass;
    }

    public void DoStuff()
    {
        this.mainClass.DoStuff(this.subClass);
    }
}

public class MainClass : IMainClass
{
    public void DoStuff(ISubclass subclass)
    {
        // do something with transient subclass
    }
}

public class SubClass : ISubClass
{ }

Registry

public class MyRegistry : Registry
{
    public MyRegistry()
    {
        // The root of the object graph is transient...
        For<IInteractionManager>()
            .Transient()
            .Use<InteractionManager>();

        // ...therefore, it can have transient dependencies...
        For<ISubClass>()
            .Transient()
            .Use<SubClass>();

        // ...and singleton dependencies.
        For<IMainClass>()
            .Singleton()
            .Use<MainClass>();
    }
}
Katherine answered 27/3, 2016 at 15:33 Comment(1)
Captive dependencies definitely explains the problem. Thanks a lot for the article. Unfortunately the interface IMainClass is from a nuget package, so I cannot change it. But now that I understood the issue, I should be able to work something out...Mouflon
C
2

If you really need MainClass to be a long lived object, but use a new SubClass object per request, you can either:

1.) Register a custom instance of Func to the container, or to the registration of MainClass itself, that delegates to the root Container itself. Remember that you can use auto-wiring too so you don't have to do it all inline.

2.) Inject the IContainer itself into MainClass and use it as a service locator to lazily retrieve a new ISubClass during each request.

Using IContext the way that you are won't work because the IContext is tied to the original request. "Transient" in StructureMap really means "per request," so when you inject the Func into your singleton that delegates to the original IContext, you'll get the exact same ISubClass every time. See http://structuremap.github.io/object-lifecycle/ for more information on lifecycles.

I'm the SM author btw.

Camarillo answered 27/3, 2016 at 19:7 Comment(0)
K
1

It seems that you need to rework the design a little. To avoid having captive dependencies, you need to give the root object of the graph a shorter (or equal) lifetime than any of its dependencies.

One option would be to make a 3rd class to manage the interaction between MainClass and SubClass.

Interfaces

public interface IInteractionManager
{ 
    void DoStuff();
}

public interface IMainClass
{ 
    void DoStuff(ISubclass subclass);
}

public interface ISubClass
{ }

Classes

public class InteractionManager : IInteractionManager
{
    private readonly IMainClass mainClass;
    private readonly ISubClass subClass;

    public InteractionManager(
        IMainClass mainClass,
        ISubClass subClass)
    {
        this.mainClass = mainClass;
        this.subClass = subClass;
    }

    public void DoStuff()
    {
        this.mainClass.DoStuff(this.subClass);
    }
}

public class MainClass : IMainClass
{
    public void DoStuff(ISubclass subclass)
    {
        // do something with transient subclass
    }
}

public class SubClass : ISubClass
{ }

Registry

public class MyRegistry : Registry
{
    public MyRegistry()
    {
        // The root of the object graph is transient...
        For<IInteractionManager>()
            .Transient()
            .Use<InteractionManager>();

        // ...therefore, it can have transient dependencies...
        For<ISubClass>()
            .Transient()
            .Use<SubClass>();

        // ...and singleton dependencies.
        For<IMainClass>()
            .Singleton()
            .Use<MainClass>();
    }
}
Katherine answered 27/3, 2016 at 15:33 Comment(1)
Captive dependencies definitely explains the problem. Thanks a lot for the article. Unfortunately the interface IMainClass is from a nuget package, so I cannot change it. But now that I understood the issue, I should be able to work something out...Mouflon
D
0

This is very normal.

Because you are resolving ISubclass in Main class which is singleton.

When Main Class resolved it will get one Subclass and they will live together(they have to because of main is singlenton).

When you resolve you should check first parent lifetime if it's singleton it doesn't matter you have per request or per dependency, it will live with singleton.

Dorladorlisa answered 27/3, 2016 at 12:2 Comment(1)
Well actually no. As I don't pass an instance of SubClass to the constructor of MainClass. Instead I pass the method to retrieve an instance of SubClass. And this method is in fact called each time (I checked that). The only problem is, ctx.GetInstance<ISubClass>() obviously is not aware of the request context.Mouflon

© 2022 - 2024 — McMap. All rights reserved.