简体   繁体   中英

Winforms IOC Container - Composition Root

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:

  1. Is my new way of "higher level" thinking, and bootstrapping from Program.cs now correct
  2. How Can I handle optional parameters in LightInject
  3. Is How I have setup my factory the correct way to do it?
  4. Let's forget about the Fugliness of the factory and just try to work on the mechanics of the questions :)

I know it's a bit late to answer a question that is over a year old but let me try.

The issue here is that you don't want your container to go out anywhere else than your Composition Root. In a complex solution consisting of mutliple assembiles, this means the container itself is only referenced by the topmost assembly (where the Composition Root is).

But the application stack is usually complex, you possibly have multiple assembiles and still, your depencencies should be resolves across the application.

Historically, one of the possible approaches was the Service Locator pattern. The locator goes down to the very bottom of the stack and from there, it offers a service that resolves dependencies. Thus, it's available anywhere up the stack.

This approach has two drawbacks, first is that your container is referenced at the very bottom of the stack and even if you circuvment this you still have your locator referenced everywhere. The latter could be painful in a large app as you possibly have some standalone assembiles that you don't want to be forced to reference your locator (or anything else).

The ultimate solution is called the Local Factory (aka Dependency Resolver) and it takes care of creating just few of its dependand services. The trick is then to have multiple local factories across your app.

A typical setup is like this. Suppose there's an assembly, call it A , that the client will use to obtain an instance of IServiceA . The assembly contains only the two:

  • interface (obligation) of the service - IServiceA
  • the local factory clients will use to obtain instances of the service

And that's all, no other references, no containers. There's even no implementation at this point yet. The trick here is to make the factory open for the actual provider - in a sense that the factory doesn't even yet know how to create instances - it's the Composition Root that will tell it.

// Assembly A

public interface IServiceA
{
   ...
}

public class ServiceAFactory
{
    private static Func<IServiceA> _provider;

    public static void SetProvider( Func<IServiceA> provider )
    {
        _provider = provider;
    }

    public IServiceA Create()
    {
        return _provider();
    }
}

the provider here has a functional contract but it could also be expressed as an interface

And that's all, although there's no implementation in the factory at the moment, the client code is suddenly very stable:

// client code to obtain IServiceA
var serviceA = new ServiceAFactory().Create();

Note again how self-contained this assembly A is. It has no other references, still, it offers a clean way to obtain instances of your service. Other assemblies can reference this assembly with no other additional references.

Then comes the Composition Root .

At the very top of your stack, your main assembly references the assembly A and some other assembly, let's call it AImpl that contains a possible implementation of the service interface.

technically the implementation of the service could be in the very same assembly the interface is but it only makes things easier

The Composition Root creates the provider of the factory by delegating a factory method down the stack, to the assembly A

 // Composition Root in the top level module
 // Both assemblies
 //    * A     that contains IServiceA
 //    * AImpl that contains an implementation, ServiceAImpl
 // are referenced here 
 public void CompositionRoot()
 {
      ServiceAFactory.SetProvider( () =>
         {
             return new ServiceAImpl();
         } );
 }

From now on, the provider is set up and all the client code down the stack that uses the factory, can succesfully obtain instances.

The Composition Root provides all other implementations of other local factories, as well. There are multiple setups then in the Composition Root:

    SomeLocalFactoryFromAssemblyA.SetProvider( ... );
    AnotherLocalFactoryFromAssemblyB.SetProvider( .... );
    ...

Where is your container then?

Well, the container is just one possible implementation of the provider. It only helps rather than spoils. Note however that you don't even have to use it, it's a choice rather than obligation.

 public void CompositionRoot()
 {
      var container = new MyFavouriteContainer();
      container.Register<IServiceA, ServiceAImpl>(); // create my registrations

      ServiceAFactory.SetProvider( () =>
         {
             // this advanced provider uses the container
             // this means the implementation, the ServiceAImpl,
             // can have possible further dependencies that will be
             // resolved by the container
             container.Resolve<IServiceA>();
         } );
 }

This is the most clean setup I am aware of. It has all desired features:

  • it separates concerns in a clean way
  • the client doesn't really need any other dependencies than the service contract and the factory
  • the client doesn't even know there is or will be a container, in fact the client doesn't care
  • in a test environment, providers can easily be setup without any container, to provide static mocks of your services
  • the Composition Root is a real composer here - it's the only place in your code where the three: interfaces, implementations and the container, meet together

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM