I've recently been dabbling a bit with IOC Containers (LightInject in my case).
I've been reading that you should only need to use the container ONCE, on startup, and no where else. This is what I'm struggling to understand. If I can only reference the container in a bootstrap/startup method, how is it possible to resolve what I need, elswhere in the project, or at runtime if the class depends on user input.
So In my Traditional Windows Forms App, on Form Load Say, I would Bootstrap Lightinject as per the below code. It's only an arbitrary example, it's more the premise I need to get my head around.
I might be missing something here entirely, or just not getting it. But how am i supposed to resolve dependancies, If i can't use/not supposed to reference or use Container.GetInstance/Resolve/{Choose IOC Syntax Here}, and only in the composition root.
For Instance, Say I have two buttons and a TextBox on my form. The first button gets me an ILoader (below code), and the second button loads up a file viewer (ILoader, below code), whose file name is what is entered into the textbox on the winform.
Without An IOC Container I would do the following (let's just assume its put in the click event)
Button 1 Click Event :
ISplitText MyStringFunc = new WhateverImplementsIt();
Button 2 (gets the file reader based on textbox input)
ILoader MyLoader = new FileReaderImplementation(TextBox1.Text);
Using LightInject, I'm surely compelled to do the following:
Button1 Click:
ISplitText Splitter = Container.GetInstance<ISplitText>();
Button 2 Click
var LoaderFunc = Container.GetInstance<Func<string, ILoader>>();
ILoader l2 = LoaderFunc(TextBox1.Text);
Am I Incorrect? In A large project I would have Container.GetInstance, peppered all over the place, in the main form file and elsewhere surely, so how can i only reference the container in ONLY 1 spot, in the form of bootstrap, am i missing a magic piece of the puzzle?
In all the sample apps I have seen it's all done in one simple console app, in the Main function. All these apps follow the format of:
Container = new Container();
Container.Register<IFoo,Foo>();
Container.Register<IBar,Bar();
var Resolved = Container.GetInstance<IFoo>();
Well, I understand all that, and it's extremely simple. It's once you start adding a bit of complexity to the app itself, I'm lost as to how to get the instances without making the Container itself public, or static, or accessible in some way,shape or form and then calling Container.GetInstance in a million places (which apparently, is a big no no). PLEASE HELP! Cheers,
Chud
PS - I am not concerned about "abstracting the container" itself. so would prefer to only focus on increasing my understanding of the above.
public class BootStrapIOC
{
public ServiceContainer Container;
public BootStrapIOC(ServiceContainer container)
{
Container = container;
}
public void Start()
{
Container.Register<ISplitText, StringUtil>();
Container.Register<string, ILoader>((factory, value) => new FileViewByFileName(value));
}
}
//HUH? How can i NOT use the container??, in this case in the button_click
ILoader Loader = Container.GetInstance<Func<string, ILoader>>();
ILoader l2 = Loader(TextBox1.Text);
ISplitText Splitter = Container.GetInstance<ISplitText>();
EDIT #1
OK, so, after re-reading the comments and looking at a few more examples on the interweb, I think I may finally understand it. The issue was (I think) is that i wasn't thinking "higher level" enough. I was trying to resolve my dependancies in my winforms application, AFTER the form had already been constructed,and in the form itself. When in reality, it's too late by then. I wasn't viewing the "form itself" as just another object, which needed it's dependencies injected into it.
So I bootstrap now in my Program.cs:
static class Program
{
private static ServiceContainer Container;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Container = new ServiceContainer();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
BootStrapIOC Strap = new BootStrapIOC(Container);
Strap.Start();
//This magic line resolves EVERYTHING for me required by the Form
var form = Container.GetInstance<Form1>();
Application.Run(form);
//Application.Run(new Form1());
}
}
My question now is, Is my line of thinking now correct in terms of winforms. It seems to make more sense, changing my approach to "higher up" the chain and resolving from Program.cs??
Secondly, And I'm not sure if this calls for a new question altogether, please advise as I am an SO noob.
How would I setup a factory to return the correct instance of an object? One of the original comments indicated that that would be a usage in this scenario. Let's use a contrived example. Where I needed an object, but don't know which object until run time/user input.
My Idea:
BootStrap Container.Register();
Factory Interface and Implementation: Let's put some optional parameters in also, as I want to know if this is the correct/best way to do it?
public interface IFileViewerFactory
{
ILoader GetFileViewer(string FileName, string Directory = null, bool CreatingDirectory = false);
}
public class FileViewerFactory:IFileViewerFactory
{
public FileViewerFactory() { }
public ILoader GetFileViewer(string FileName, string Directory = null, bool CreatingDirectory = false)
{
if (CreatingDirectory == false)
{
if (Directory == null)
return new FileViewByFileName(FileName);
else
return new FileViewByDirectoryName(Directory, FileName);
}
else
return new FileViewByDirectoryNameCreateDirectoryOptional(Directory, FileName, CreatingDirectory);
}
}
Form:
public IFileViewerFactory FileViewerFactory { get; set; }
Button Click:
ILoader FileLoader = FileViewerFactory.GetFileViewer(TxtBoxFileName.Text);
Or:
ILoader FileLoader = FileViewerFacotry.GetFileViewer(TxtBoxFileName.Text,TxtBoxDirectory.Text);
So to finish off, my questions are:
- Is my new way of "higher level" thinking, and bootstrapping from Program.cs now correct
- How Can I handle optional parameters in LightInject
- Is How I have setup my factory the correct way to do it?
- Let's forget about the Fugliness of the factory and just try to work on the mechanics of the questions :)
ISplitText
can be constructor injected. if the concern is about deferring the creation of the dependency, that is what factories are for. But factories tent to be misleading as they are not the true dependency, they are the creator of the dependency. implementations should only depend on what they actually need – Criminate