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

3

8
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! :)

Chor answered 14/8, 2009 at 16:14 Comment(3)
What was the eventual solution? I'm also looking for a way to use UserControls in the designer and also use dependency injection.Trampoline
Please let us know if you found a feasible solution. Thanks!Participial
Basically, I used the designer to drag user controls onto the form and exposed these controls as properties of the form. Then near my application entry point I bind the values of these properties to the view. Then when the container (I use Autofac now) needs an object of the view type (for the view's presenter), it simply loads the value of the Form's property. Side note: I also use events and don't expose the Presenter to the View anymore, to eliminate unncessary coupling of the view to the presenter. The view should not know that there is a presenter at all.Chor
C
2

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.

Cygnet answered 15/8, 2009 at 11:0 Comment(0)
A
4

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 );
        }
    }
}
}
Alfons answered 25/11, 2015 at 23:45 Comment(0)
C
2

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.

Cygnet answered 15/8, 2009 at 11:0 Comment(0)
S
1

I recently created some reuseable UserControls with the need of Dependencies to inject. As the IoC Container isn't used to create those UserControls, obviously he cannot automatically inject the Dependencies.

My Solution is, a Base-Class to enable at least Property Injection. Constructor Injection is not supported, as a parameterless Constructor is used to create those instances.

public class NinjectUserControl : UserControl
{

    // Generally this is considered to be a bad practice, 
    //however I didn't find any better way. If you do, please share :)
    public static IKernel Kernel { private get; set; } 

    protected override void OnInitialized(EventArgs e)
    {
        base.OnInitialized(e);
        RequestActivation(Kernel);
    }

    protected virtual void RequestActivation(IKernel kernel)
    {
        kernel?.Inject(this);
    }
}

To get it to Work you need to set the Kernel once. Typically this is somewhere inside your program.cs (WinForms) or App.xaml.cs (WPF)

IocKernel = new StandardKernel(); // typically a static member
NinjectUserControl.Kernel = IocKernel;
IocKernel.Load(new Module()); // loading modules
// .. Create MainForm or whatever

To use, simply inherit NinjectUserControl and then let the Kernel Inject your dependencies via Property Injection:

[Inject]
public IService Service { private get; set; }

please note, that those dependencies are not accessible inside the Constructor.

Surfbird answered 18/8, 2017 at 7:11 Comment(1)
Note that the Framework I used to implement this solution is WPF, However this should work pretty similar using WinFormsSurfbird

© 2022 - 2024 — McMap. All rights reserved.