simpleinjector / SimpleInjector

An easy, flexible, and fast Dependency Injection library that promotes best practice to steer developers towards the pit of success.
https://simpleinjector.org
MIT License
1.22k stars 152 forks source link

Container Constructing Two Instances Of Class that's only registered once #904

Closed ghost closed 3 years ago

ghost commented 3 years ago

Describe the bug

I'm using Windows Forms, I'm manually registering all of my services in a row except for my windows forms. I'm using the autoRegisterWindowsForms() method from the Stack Overflow question thread.

private static void autoRegisterWindowsForms()
{
    var types = _container.GetTypesToRegister<Form>(typeof(Program).Assembly);

    foreach (var type in types)
    {
        var registration =
            Lifestyle.Transient.CreateRegistration(type, _container);

        registration.SuppressDiagnosticWarning(
            DiagnosticType.DisposableTransientComponent,
            "Forms should be disposed by app code; not by the container.");

        _container.AddRegistration(type, registration);
    }
}

I have a Form which is my main form titled ClientForm, it's hitting the constructor twice. This service is never explicitly registered, the only two references to ClientForm is the GetInstance<ClientForm>() and the class name.

If I step through on the GetInstance it constructs everything in the application, but for some reason constructs ClientForm in the middle and in the end. Checking the other forms they only construct once. ClientForm is not implementing any interface, it is only inheriting Form, which is not registered.

Expected behavior

I expect it to only initialize this class once as it's only registered once.

Actual behavior

While constructing all registered services, it constructs ClientForm twice.

To Reproduce

I do not know how to reproduce this issue. I can share my source code on request.

Additional context

dotnetjunkie commented 3 years ago

This service is never explicitly registered

Your ClientForm class is registered by the autoRegisterWindowsForms() method. If you debug through it, you'll notice that GetTypesToRegister<Form> returns an array containing the type of ClientForm. Further down the method, the call to AddRegistration adds ClientForm as registration.

If I step through on the GetInstance it constructs everything in the application

This is happening because by default Simple Injector automatically verifies its configuration upon first resolve.

If this behavior leads to unexpected behavior, you can turn it off by setting Container.Options.EnableAutoVerification to false. After that you can call Container.Verfiy() from within a unit test instead.

Checking the other forms they only construct once.

That's because the call to Container.GetInstance<ClientForm>() verifies all registrations by (among other things) creating each class once. But after verification has completed, the container will continue with the normal resolution process, which means creating the class that is requested, hence ClientForm.

If you have any additional questions, please let me know.

p.s. instead of Stack Overflow answers, there is a documentation page on Windows Forms as well.

ghost commented 3 years ago

That was exactly the issue, thank you very much!

Moving on from here I will be likely submitting another request for more information doing property injection on User Controls, it says to use explicit property injection using attributes, and I can do that, but the controls themselves are still going to be constructed by the Designer, if I choose for them to construct via the injection, then I cannot configure those on the forms as normal. Unless this is expected behavior.

Thank you.

dotnetjunkie commented 3 years ago

Although the Windows Forms documentation does state you should use property injection, it unfortunately lacks information on how to actually do this, because, as you rightfully pointed out, User Controls can't be created by the container.

The trick here is to configure property injection (as explained in the documentation) and call Registration.InitializeInstance on a created user control. The Web Forms documentation actually shows an example.

Here's an example:

var container = new Container();
container.Options.PropertySelectionBehavior = new ImportAttributePropertySelectionBehavior();

// Register your user controls. Still needed.
container.Register<MyUserControl>();

Later on, after user control is created:

var reg = Container.GetRegistration(typeof(MyUserControl), true).Registration;
reg.InitializeInstance(userControlInstance);