Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using IoC/DI Containers with run-time dependent constructor arguments

I'm converting my code to use an IoC Container with StructureMap. Trying to get my head around things, and I feel it's starting to 'click' and I can see how it makes so much sense with the back-end side.

However, I'm working my way down I've spotted a few sitautions where I'm not sure how to make it work. Specifically, the case where my original constructor did something important with an argument that isn't really a dependency, or something that will change at run-time.

Let's say I start with this (pre IoC Container), where I was passing my dependencies using a constructor, but also sending it an ImportantObject that's run-time dependent:

IMyPageViewModel myPageViewModel = new MyPageViewModel(importantObject, dialogManager, pageDisplay, viewModelProvider)

and here it is doing its constructing:

public MyPageViewModel(ImportantObject importantObject, IDialogManager dialogManager,IPageDisplay pageDisplay, IViewModelProvider viewModelProvider)
{
    this.dialogManager = dialogManager;
    this.pageDisplay = pageDisplay;
    this.viewModelProvider = viewModelProvider;

    importantObject.DoThatImportantThing();
}

Now, I'm migrating to use the IoC container, and at first I think I should do something like this:

//I need to create an instance to use, so I use my IoC container:
IMyPageViewModel myPageViewModel = container.GetInstance<IMyPageViewModel>();

then letting it resolve it's dependencies, however importantObject is something that's set at runtime. I can't register it as a dependency:

public MyPageViewModel(IDialogManager dialogManager,IPageDisplay pageDisplay, IViewModelProvider viewModelProvider, IContainer container)
{
    this.dialogManager = dialogManager;
    this.pageDisplay = pageDisplay;
    this.viewModelProvider = viewModelProvider;

    //however, here I have no access to the important object that I previously passed in my constructor
    importantObject.DoThatImportantThing(); //obviously error
}

I thought maybe I should be creating using 'new', and passing the IoC container:

IMyPageViewModel myPageViewModel = new MyPageViewModel(importantObject, container)

then letting it resolve it's dependencies in the constructor:

public MyPageViewModel(ImportantObject importantObject, IContainer container)
{
    this.dialogManager = container.GetInstance<IDialogManager>();
    this.pageDisplay = container.GetInstance<IPageDisplay>();
    this.viewModelProvider = container.GetInstance<IViewModelProvider>();

    importantObject.DoThatImportantThing();
}

But that strikes me as not a good idea, specifically, I can't run it with a test register and have it create a dummy/stub "MyPageViewModel" for unit testing.

The only other thing I can think of is to remove all the logic from the constructor and putting it in an initialize method, or property setters. However, this means I have to ensure that initialize is always called before use and it will hide errors/problems.

Are any of these options sensible, how should I manage passing a run-time dependent object in a constructor with Dependency Injection?

I tried to stray away from static factories, as I've read lots about them being anti-pattern/bad practice.

Edit: In response to Bruno Garcia's answer I decided to use a factory type pattern that holds the container and deals with object creation like this:

class PageProvider : IPageProvider
{
    public MyPageViewModel GetMyPage(ImportantObject importantObject)
    {
        //might just get, if it's a single only instance
        return MyPageViewModel(ImportantObject importantObject,
                               container.GetInstance<IDialogManager>(),
                               container.GetInstance<IPageDisplay>(),
                               container.GetInstance<IViewModelProvider>())
    }
}
like image 931
Joe Avatar asked Nov 09 '22 15:11

Joe


1 Answers

StructureMap supports passing arguments to Resolve. This could help you with passing the ImportantObject to the Service you are resolving.

It's worth noting that if you pass your container around, things can get really messy really fast. Avoid using it as a Service Locator.

Ideally you'd use the container to resolve an entry point (e.g: Controller, Consumer worker) and from that point on, there's no direct use of the container anymore. If you need to control the lifetime of the dependencies you are taking into your constructor, there are a number of ways to go about that like: Taking a Factory or a Func<>.

I suggest you reading carefully the docs of the Container you want to use to understand who controls the lifetime of objects (if a Component implements IDisposable, who's going to dispose it?). When are lifetime scopes created/disposed?

IoC Container's are great but it's very easy to find yourself troubleshooting memory leaks if you don't carefully understand the concept of lifetime ownership.

like image 66
Bruno Garcia Avatar answered Nov 14 '22 22:11

Bruno Garcia