Dependency Injection in WinForms using Ninject and Entity Framework
Asked Answered
S

1

19

I am currently working on a project where we are converting our old DataSet, Stored Procedure WinForm application to use Entity Framework so new websites can access the same object model and repositories.

Anyway, I am trying to implement Dependency Injection into the Forms so that we can use mocked Repositories for unit testing. I am using Ninject for the the simple reason I have used it before in MVC websites, however trying to implement this in the WinForm application is proving to be problematic to say the least, hampered even more by the lack of information on DI in WinForms on the web.

So far I have created the Ninject Factory and repositories, but I haven't had much luck injecting the repositories into forms.

Therefore can anyone help me or make any suggestions?

Below I have parts of my code that might help:

Ninject Factory:

public class NinjectFactory : NinjectModule
{
    private IKernel _ninjectKernel;

    public NinjectFactory()
    {
        _ninjectKernel = new StandardKernel();
    }

    public override void Load()
    {
        _ninjectKernel.Bind(typeof(IRepository<>)).To(typeof(GenericRepository<>));
        _ninjectKernel.Bind(typeof(IProductionRepository)).To(typeof(ProductionRepository));
    }
}

Form with repositories:

Public Class TaskForm
    Inherits BaseBusinessDialogForm

    Private _repository As TaskRepository
    Private _task As Production.Task = Nothing

    Public Sub New(ByVal task As Production.Task)

        InitializeComponent()

        _repository = New TaskRepository(ConnectString)

        If task.TaskID = 0 Then
            _task = task
        Else
            _task = _repository.GetByID(task.TaskID)
        End If

        MyBase.BusinessObject = _task
        Me.TaskBindingSource.DataSource = MyBase.BusinessObject

    End Sub

Class that launches the MDI form which holds the above form:

Dim kernel As IKernel = New StandardKernel(New NinjectFactory())
''Dim kernel As IKernel = New StandardKernel(New NinjectFactoryTest())
mfrmMDI = kernel.Get(Of Forms.MDI)()

Application.DoEvents()
mfrmMDI.ShowDialog()

I understand that my question is a bit vague, but I'm not sure where the problem lies or what I need to complete.

Thanks very much

Scientist answered 2/1, 2013 at 19:14 Comment(2)
you have a mix of VB and C#, would it be ok to have answers purely on C#?Lammergeier
Sorry for the mix of languages, it's straight from the project code. The old stuff is in VB and the newer in C#, annoying to say the least!Scientist
L
37

You create composition root as one entry point for your resolutions. You pass INjectModule as a parameter so that you can configure it it tests differently. One of the benefits of Composition Root is that not all of your assemblies will depend on NInject and you will have one single point to change resolution logic. It is really a cool pattern, when you might change IoC container or introduce some dynamic interception in future.

public class CompositionRoot
{
    private static IKernel _ninjectKernel;

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

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

Your module would look like this

public class ApplicationModule : NinjectModule
{
    public override void Load()
    {
        Bind(typeof(IRepository<>)).To(typeof(GenericRepository<>));
    }
}

In main method you pass ApplicationModule as a parameter and resolve Form1 and start it.

[STAThread]
static void Main()
{
    CompositionRoot.Wire(new ApplicationModule());

    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    Application.Run(CompositionRoot.Resolve<Form1>());
}

In Form1 constructor you pass required repository with specific closed generic parameters

public partial class Form1 : Form
{
    private IRepository<Process> _processRepository;

    public Form1(IRepository<Process> productionRepository)
    {
        this._processRepository = productionRepository;        
        InitializeComponent();
    }  

    private void button1_Click(object sender, EventArgs e)
    {
        MessageBox.Show(_processRepository.ToString());
    }
}

Your repositories could be very complex, but I won't add any functionality to them, instead of ToString() method so that we could see if a dependency was resolved correctly. Note there are no attributes whatsoever on repositories.

public interface IRepository<T>
{
}

public class GenericRepository<T> : IRepository<T>
{
    public override string ToString()
    {
        return "MyRepository with type : "+typeof(T).Name;
    }
}

Now when you run your application, you will see, that all has wired up and message box shows an enclosed type as Process

enter image description here

Lammergeier answered 4/1, 2013 at 12:41 Comment(10)
Thanks very much, I have managed to implement a version of your example successfully.Scientist
I think you're missing the point of what a Composition Root is intended to do. Your "Composition Root" is actually a service locator, which is often considered an antipattern. A true composition root is the "top level" object in your running application's object graph, and should be the only object that you explicitly ask for from the kernel (factories excluded). Everything else should be resolved via injection when the root is constructed, or via instantiation via a factory.Haematogenous
My CompositionRoot is not a service locator. Form1 ask IRepository<Process> as constructor argument, not CompositionRoot. CompositionRoot resolves a complete objects graph at the start of the application. You don't ask CompositionRoot from the kernel, but instead you ask the top objects from CompositionRoot and it provides a complete resolved object graphs for you.Lammergeier
The method public static T Resolve<T>() { } makes it a service locator, especially considering there's no appreciable logic in your CompositionRoot class to handle composing modules. There's nothing to "compose" in your composition root, there's only one module. We're likely arguing semantics here. In a simple winforms app, the main form serves as the composition root, since it is the "root" of the object graph and all objects reachable from it are instantiated either by constructor injection or instantiation via a factory.Haematogenous
Does public T Get<T>() in IKernel makes it a service locator? If it is static it doesn't mean, that it is service locator. I don't want to use Form1 as a composition root because it has it's own logic, which you don't want to touch in tests. Also, in tests you should also create yet another CompositionRoot?Lammergeier
I think i've got what you mean. Mark Seeman provide similar example of Service Locator at his blog.ploeh.dk/2010/02/03/ServiceLocatorIsAnAntiPattern.aspx. But there are key differences - SL uses dictionary, which is crutial, also in constructor you call SL.Resolve<T> while call I call Root in one place and nowhere else. If any misunderstandings left - please noteLammergeier
@IlyaIvanov, this looks nice in this simple scenario. I would like to know what you think a best practice would be if my "main" form would have to create sub forms, also using DI. How would I avoid calling the CompositionRoot.Resolve method there (which would be treated as the "evil service locator")? Thank you for your update.Abuttal
The creation of objects after the top level object has been created is usually done using factories. There is a factory extension to NInject which makes this convenient github.com/ninject/ninject.extensions.factory/wiki equivalent to Autofac DelegateFactory.Sectarian
@IlyaIvanov why are you not implementing the "Register, Resolve, Release" methodology that Seemann seems to suggest we must when using a DI Container (Section 3.3.2 in his book)? I am totally new to this and am following what you have provided above, but am slightly confused over the use of Factories against what you are doing here? Why do we need this? I also assume I am free to implement a "Release" method in CompositionRoot and implement IDisposible which merely calls ninjectKernel.Dispose()?Avron
Ps. I have a question with an open bounty here #35802680Avron

© 2022 - 2024 — McMap. All rights reserved.