Building a WinForms Application using MVC and Ninject as IoC Container
Asked Answered
D

1

3

I am having to re-write a large WinForms application and I want to use MVC to allow increased testing capability etc. I want to also adopt Ninject as my IoC container as it is lightweight, fast and will increase the exstensibility of my application going forward.

I have done a great deal of reading and I have managed to make a start on the arcitecture of this new application. However, I am not sure i have the right idea when using Ninject. The code...

Starting with Program.cs and related classes...

static class Program
{
    [STAThread]
    static void Main()
    {
        FileLogHandler fileLogHandler = new FileLogHandler(Utils.GetLogFilePath());
        Log.LogHandler = fileLogHandler;
        Log.Trace("Program.Main(): Logging initialized");

        CompositionRoot.Initialize(new ApplicationModule());

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(CompositionRoot.Resolve<ApplicationShellView>());
    }
}

public class CompositionRoot
{
    private static IKernel _ninjectKernel;

    public static void Initialize(INinjectModule module)
    {
        _ninjectKernel = new StandardKernel(module);
    }

    public static T Resolve<T>()
    {
        return _ninjectKernel.Get<T>();
    }
}

public class ApplicationModule : NinjectModule
{
    public override void Load()
    {
        Bind(typeof(IApplicationShellView)).To(typeof(ApplicationShellView));
    }
}

An my ApplicationShellView is

public partial class ApplicationShellView : Form, IApplicationShellView
{
    public ApplicationShellView()
    {
        InitializeComponent();
    }

    public void InitializeView()
    {
        dockPanel.Theme = vS2012LightTheme;
    }
}

with interface

public interface IApplicationShellView
{
    void InitializeView();
}

The controller for this view is

public class ApplicationShellController
{
    private IApplicationShellView view;

    public ApplicationShellController(IApplicationShellView view)
    {
        view.InitializeView();
    }
}

Currently the controller is redundant, and although this code works and my view displays, I have some important questions...

  1. Should I be using the ApplicationShellController to initialize my form, currently this is not using MVC "pattern"?
  2. It feels like I have written a Service Locator, and from what I have read, this is bad. How else should I be using Ninject for IoC to initialize my application?
  3. Any other advice as to what I am doing right[if anything!]/wrong?

Thanks very much for your time.

Dipetalous answered 4/3, 2016 at 17:20 Comment(11)
i think this / a similar question was asked before, especially regarding "Service Locator". Generally this question is/should be more about how to do MVC with WinForms in conjunction with any DI container...Sugary
This is not a generic question. This is a question concerning the specific situation. How can you say it has been asked before? My question is not specifically regarding a service locator, it is how to couple MVC with an IOC container.Dipetalous
Can you give an example about how you use another forms or services in your ApplicationShellView without DI ? (I'm not experienced about winforms, I should see the life managment).Flaunch
So what i meant is that i believe that the most difficult part about this question is how to combine MVC + DI-container with winforms (Regardless of which DI container one uses). And i think there's already been questions covering that aspect. The ninject-specific part is very small and i'd be happy to help you with that. But same as @MaDeRkAn i don't know enough about WinForms to do so.Sugary
How about this?Sugary
@Sugary I have used this question/answer to start my application, but as the comments on the accepted answer suggest, the methodology is deemed to be a "Service Locator", that is my issue. I want to know how it is done using "pure" dependency injection, without the introduction of a service locator "anti-pattern". I also would like to know how to incorporate MVC into any use of the IoC container. I will edit my question shortly to show some more details of what I am doing now. Thanks very much for your time...Dipetalous
after configuration of the container there is always at least one request like kernel.Get<...> where the application-root is retrieved - this looks exactly like service locator but it also perfectly acceptable. The idea is that you should limit the service-locator calls to a minimum, ideally building the whole object graph in one go. Also, all container usage should reside in the Composition Root - which is not a single class (!!). Also see here and here.Sugary
Regarding the late-creation of objects Mark Seeman also wrote an article on how to implement an AbstractFactory.Sugary
I have recently obtained Seemann's book and in chapter 3 he speaks of the use of the DI Container as a pattern. He says that CompositionRoot is the only place the IoC containers Get or Resolve method should be used. He calls this the "Three Calls Pattern" (Register, Resolve, Release). It is not clear to me however, how this "pattern" is different from the "service locator anti-pattern"? You say that the "CompositionRoot" is "not a single class", but Seemann seems to suggest it is...Dipetalous
So I am left pondering as there is much conflicting information on the web. I think I will follow what Seemann is saying in his book, I can't go far wrong then... Thanks for your time.Dipetalous
Let us continue this discussion in chat.Sugary
S
2
  1. No you should not be initializing your controller, this exactly what IoC and Ninject are for. When you initialize your view/form, Ninject should make the view fetch the controller it depends on, which will auto fetch controllers it depends on and so on.
    Of course this won't work like you've set it up right now. For starters, your view needs to know the controller it depends on.

    public partial class ApplicationShellView : Form, IApplicationShellView
    {
        private IApplicationShellController _controller;
    
        public ApplicationShellView()
        {
            InitializeComponent();
            init();
    
            //InitializeView()
        }
    
        private void init() {
            _controller = NinjectProgram.Kernel.Get<IApplicationShellController>();
            //Because your view knows the controller you can always pass himself as parameter or even use setter to inject
            //For example:  _controller.SetView1(this);
        }
    
        public void InitializeView()
        {
            dockPanel.Theme = vS2012LightTheme;
        }
    }
    
    public class ApplicationShellController : IApplicationShellController
    {
    
        //Implementes functionality for the MainForm.
    
        public ApplicationShellController()
        {
            //Also possible to add other controllers with DI
        }
    }
    
  2. This does indeed look like a Service Locator, simply initializing your view should do be sufficient.

    public class NinjectProgram
    {
        //Gets the inject kernal for the program.
        public static IKernel Kernel { get; protected set; }
    }
    
    public class Program : NinjectProgram
    {
        [STAThread]
        private static void Main()
        {
            Kernel = new StandardKernel();
            Kernel.Load(new ApplicationModule());
    
            Application.Run(new ApplicationShellView());
        }
    }
    
    public class ApplicationModule : NinjectModule
    {
        public override void Load()
        {
            //Here is where we define what implementations map to what interfaces.
            Bind<IApplicationShellController>().To<ApplicationShellController>();
    
            //We can also load other modules this project depends on.
            Kernel.Load(new NinjectModule());
        }
    }
    
  3. Don't try and make it too complicated, a good start is important but you can always apply changes when and where needed during development.

I believe the following GitHub project might be a good starting point: Example of how you might use Ninject within a WinForms application.

If you have any more questions, just leave a comment and I'll try to answer them as soon as possible

Scrannel answered 7/3, 2016 at 15:2 Comment(4)
I have now got hold of Seemann's book "Dependency Injection for .NET" and it seems to suggest using a DI Container/Composition Root much like I have above. The book suggests that this CompositionRoot should be the only place where the IoC container's Resolve (Get for Ninject) method should be used. It then suggest using the "Three Calls Pattern" using this "CompositionRoot", what I don't get is when this pattern becomes the "Service Locator Anti-Pattern"? It seems from what I have read in chap. 3 of this book, that we should not use the kernel calls outside of CompositionRoot. Thanks a lotDipetalous
@Killercam i concur that Mark's saying that when the kernel is used outside of the "Composition Root" it becomes an anti-pattern. And i (mostly) concur with his opinion, too. Personally i sometimes violate it though, for some factories, in case the downsides of dependencies to the container outweigh the benefits of putting it in the composition root.Sugary
I'm not very familiar with WinForms but since i believe @Killercam's original code is working, i'd consider the call _controller = NinjectProgram.Kernel.Get<IApplicationShellController>(); as service-location, and anti-pattern in this case. Why? Because the call can be moved closer to the main method - as in Killercam's original solution.Sugary
@Scrannel One other problem I have is that the controller must be able to act directly on the view, thus the view must be passed in as you suggest - this creates a circular reference that goes against "true" dependency injection. However, because in WinForms we don't have a binding system, this circular reference is hard to break without introducing an ugly mediator in to the frey. Should I be worried about setting controller access to the view this was (which has to be done for all controllers/views), as it is still testable and it works, or should I be doing something else?Dipetalous

© 2022 - 2024 — McMap. All rights reserved.