Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to apply dependency injection to UserControl views while keeping Designer happy?

public class StatisticsViewPresenter
{
    private IStatisticsView view;
    private Statistics statsModel;

    public StatisticsViewPresenter(IStatisticsView view, Statistics statsModel)
    {
        this.view = view;
        this.statsModel = statsModel;
    }
}

I don't use events (but am willing to if it can solve my problem), so my View classes look like this:

public class StatisticsForm : Form, IStatisticsView
{
    public StatisticsForm()
    {
        InitializeComponent();
    }

    [Inject]
    public StatisticsViewPresenter Presenter
    {
        private get;
        set;
    }
}

With

kernel.Bind<StatisticsPresenter>().ToSelf().InSingletonScope();
kernel.Bind<IStatisticsView>().To<StatisticsForm>();
kernel.Get<IStatisticsView>();

it builds up the Form, builds up the presenter, then injects the presenter into the Presenter property. Everything's peachy. (Except for that singleton-scoped presenter--any thoughts on a better way to do that? Perhaps just manually inject the presenter into the view's Presenter property inside the presenter's constructor: this.view.Presenter = this).

But if I turn StatisticsForm into StatisticsUserControl and drag-drop it onto my MainForm, it's not being injected into MainForm by Ninject, it's simply being new'd by the Designer. I see three solutions here:

1) Don't use UserControls and just use one giant form that implements these multiple views (eww);

2) Inject UserControls into my form and lose Designer support;

3) Your solution! :)

like image 618
xofz Avatar asked Aug 14 '09 16:08

xofz


People also ask

How do we implement dependency injection?

Dependency Injection is done by supplying the DEPENDENCY through the class's constructor when creating the instance of that class. The injected component can be used anywhere within the class. Recommended to use when the injected dependency, you are using across the class methods.

Which is the right way to inject dependency answer choices?

Constructor injection should be the main way that you do dependency injection. It's simple: A class needs something and thus asks for it before it can even be constructed.

Can we inject the dependency to individual action method of the controller?

You can take advantage of the ServiceFilter attribute to inject dependencies in your controller or your controller's action methods.

What is the main goal when we use dependency injection in our code?

A basic benefit of dependency injection is decreased coupling between classes and their dependencies. By removing a client's knowledge of how its dependencies are implemented, programs become more reusable, testable and maintainable.


2 Answers

My approach to use Ninject with forms, usercontrols and the designer is:

  • Use factories to create the forms (also for the usercontrols if you create some controls dinamically)
  • for the usercontrols and the forms keep the constructors without parameters and use property injection
  • add an Activation strategy to the kernel that check if ninject has just created a form or a usercontrol. If that is the case, the activation strategy iterates over the controls in the Controls property of the UserControl (or the Form) and calls Kernel.Inject(UserControl) for each usercontrol. (An Activation strategy is some code ninject executes after it has injected an object)

You can use the designer and have forms and usercontrols with dependencies injected via Ninject.

The only drawback is that you have to use property injection instead of constructor injection for the usercontrols (and the forms)

namespace Majiic.Ninject
{
public class WindowsFormsStrategy : ActivationStrategy
{
    // Activate is called after Kernel.Inject
    //even for objects not created by Ninject
    //To avoid multiple "injections" in the same nested controls
    //we put this flag to false.
    private bool _activatingControls = false;
    public override void Activate(IContext context, InstanceReference reference)
    {
        reference.IfInstanceIs<UserControl>(uc =>
        {
            if (!_activatingControls)
            {
                Trace.TraceInformation("Activate. Injecting dependencies in User control of type {0}", uc.GetType());
                _activatingControls = true;
                context.Kernel.InjectDescendantOf(uc);
                _activatingControls = false;
            }
        });
        reference.IfInstanceIs<Form>(form =>
        {
            if (!_activatingControls)
            {
                Trace.TraceInformation("Activate. Injecting dependencies in Form of type {0}", form.GetType());
                _activatingControls = true;
                context.Kernel.InjectDescendantOf(form);
                _activatingControls = false;
            }
        });
    }


}
}

Create the kernel and add the Activation Strategy

var kernel=new StandardKernel(new CommonMajiicNinjectModule());
kernel.Components.Add<IActivationStrategy, WindowsFormsStrategy>();

kernel extensions to iterate over descendents controls

namespace Majiic.Ninject
{
static public class WinFormsInstanceProviderAux
{
    static public void InjectDescendantOf(this IKernel kernel, ContainerControl containerControl)
    {
        var childrenControls = containerControl.Controls.Cast<Control>();
        foreach (var control in childrenControls )
        {
            InjectUserControlsOf(kernel, control);
        }
    }

    static private void InjectUserControlsOf(this IKernel kernel, Control control)
    {
        //only user controls can have properties defined as n-inject-able
        if (control is UserControl)
        {
            Trace.TraceInformation("Injecting dependencies in User Control of type {0}", control.GetType());
            kernel.Inject(control);
        }
        //A non user control can have children that are user controls and should be n-injected
        var childrenControls = control.Controls.Cast<Control>();
        foreach (var childControl in childrenControls )
        {
            InjectUserControlsOf(kernel, childControl );
        }
    }
}
}
like image 173
Antonio Estival Avatar answered Sep 30 '22 10:09

Antonio Estival


This is certainly an interesting area of, should I say, research. We've made ourselves a solution where we host user controls in a generic form.

Our generic form is not intended for use with the Designer. Through code we add the chosen user control to the Form dynamically.

For other frameworks you should look at Prism/Composite from the Microsoft Patterns & Practices group. Here's an article discussing extensions for WinForms.

like image 31
Peter Lillevold Avatar answered Sep 30 '22 08:09

Peter Lillevold