{"id":19511886,"url":"https://github.com/ipasechnikov/winforms-mvp-ninject","last_synced_at":"2026-04-27T18:05:56.821Z","repository":{"id":157451427,"uuid":"583501733","full_name":"ipasechnikov/winforms-mvp-ninject","owner":"ipasechnikov","description":"An example how to use MVP pattern with Ninject DI in .NET and .NET Framework WinForms application","archived":false,"fork":false,"pushed_at":"2023-01-02T05:04:50.000Z","size":27,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-23T15:50:53.922Z","etag":null,"topics":["dependency-injection","model-view-presenter","mvp","ninject","windows-forms"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ipasechnikov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-30T01:00:43.000Z","updated_at":"2025-06-03T13:18:44.000Z","dependencies_parsed_at":null,"dependency_job_id":"5c34d497-3761-461f-b368-3a82cc931fc8","html_url":"https://github.com/ipasechnikov/winforms-mvp-ninject","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ipasechnikov/winforms-mvp-ninject","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ipasechnikov%2Fwinforms-mvp-ninject","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ipasechnikov%2Fwinforms-mvp-ninject/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ipasechnikov%2Fwinforms-mvp-ninject/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ipasechnikov%2Fwinforms-mvp-ninject/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ipasechnikov","download_url":"https://codeload.github.com/ipasechnikov/winforms-mvp-ninject/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ipasechnikov%2Fwinforms-mvp-ninject/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32348058,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-27T17:12:42.749Z","status":"ssl_error","status_checked_at":"2026-04-27T17:12:41.658Z","response_time":128,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dependency-injection","model-view-presenter","mvp","ninject","windows-forms"],"created_at":"2024-11-10T23:23:07.005Z","updated_at":"2026-04-27T18:05:56.803Z","avatar_url":"https://github.com/ipasechnikov.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# WinForms app with Model-View-Presenter pattern and Ninject DI\n[![.NET Core Desktop](https://github.com/ipasechnikov/winforms-mvp-ninject/actions/workflows/dotnet-desktop.yml/badge.svg)](https://github.com/ipasechnikov/winforms-mvp-ninject/actions/workflows/dotnet-desktop.yml)\n\nThis is a small example project that shows how you can utilize MVP (Model-View-Presenter) pattern and Ninject dependency injector in your good old WinForms application.\n\nI hope this example will come in handy for other developers who want to improve code quality of their WinForms application.\n\n## Table of Contents\n\n- [WinForms app with Model-View-Presenter pattern and Ninject DI](#winforms-app-with-model-view-presenter-pattern-and-ninject-di)\n  - [Table of Contents](#table-of-contents)\n  - [Foreword](#foreword)\n  - [Getting Started](#getting-started)\n    - [Prerequisites](#prerequisites)\n      - [.NET Core or .NET Framework 4.8](#net-core-or-net-framework-48)\n      - [Visual Studio](#visual-studio)\n      - [Why Ninject?](#why-ninject)\n    - [Project structure](#project-structure)\n      - [WinFormsMvpNinject.App](#winformsmvpninjectapp)\n        - [\"Ninject/Modules\" folder](#ninjectmodules-folder)\n        - [\"Ninject/Strategies\" folder](#ninjectstrategies-folder)\n        - [\"Models\" folder](#models-folder)\n        - [\"Views\" folder](#views-folder)\n        - [\"Presenters\" folder](#presenters-folder)\n        - [\"Services\" folder](#services-folder)\n        - [\"Factories\" folder](#factories-folder)\n      - [WinFormsMvpNinject.Tests](#winformsmvpninjecttests)\n  - [Built With](#built-with)\n  - [Contributing](#contributing)\n  - [License](#license)\n  - [Acknowledgments](#acknowledgments)\n\n## Foreword\n\nRecently I've been working quite a lot on legacy WinForms applications.\nRefactoring and maintaning these applications is a crucial process.\nAfter spending some time with various codebases, I decided to incorporate better practices like MVP pattern and Dependency Injection.\n\nI've also tried MVVM (Model-View-ViewModel) pattern with WinForms but it didn't work out well. \nIt works but it looks alien and out of place in case of WinForms application.\n\n## Getting Started\n\nIn this section I'll try to explain how everything is tied together.\n\n### Prerequisites\n\n#### .NET Core or .NET Framework 4.8\n\nI use .NET Core 6 for this project but you are free to use any version of .NET Core.\nWhat's is more, actually all of my work projects utilize .NET Framework 4.8.\nSo you can even use .NET Framework. In case of .NET Framework I recommend using the latest (last) available version which is .NET Framework 4.8.1.\n\n#### Visual Studio\n\nI use Visual Studio 2022 Community Edition. If you have an older version of Visual Studio then there might be problems with openning solution.\nBut nevertheless the techniques used in the solution should be totally applicable for older versions of Visual Studio.\n\n#### Why Ninject?\n\nThere is quite a few great dependency injector projects available for C# but why did I choose Ninject?\nIt's simple, it's the only dependency injector I've ever used since a very long time ago and I find it quite easy to use. I'm not proud of these words myself.\n\nI beleive you can use any other dependency injection solution such as Autofac. If you know one dependency injector, you know pretty much all of them.\nThe problem is to make dependency injector work with WinForms.\n\n### Project structure\n\nThere are 2 projects in the solution\n\n* **WinFormsMvpNinject.App** - WinForms app\n* **WinFormsMvpNinject.Tests** - Tests for WinForms App\n\n#### WinFormsMvpNinject.App\n\nWe'll start with WinFormsMvpNinject.App at first. Its structure may look a bit complicated at first glance for an example project but it's only because I tried to make example as close to actual production application as possible.\n\n##### \"Ninject/Modules\" folder\n\nHere you place all you dependncy injection configurations. If you are reading this README then I think there is no need to explain it. But if for some reason you are a newcommer then I recommend you reading a few pages of an awesome [Ninject Wiki](https://github.com/ninject/Ninject/wiki).\n\n##### \"Ninject/Strategies\" folder\n\nThis is a \"magic\" folder that allows Ninject to work with WinForms. Basically you just copy-paste this folder into your project, configure your `Main` method to use classes from these folder and forget about it.\n\n```csharp\ninternal static class Program\n{\n    /// \u003csummary\u003e\n    ///  The main entry point for the application.\n    /// \u003c/summary\u003e\n    [STAThread]\n    static void Main()\n    {\n        // To customize application configuration such as set high DPI settings or default font,\n        // see https://aka.ms/applicationconfiguration.\n        ApplicationConfiguration.Initialize();\n\n        // Create a Ninject kernel\n        var kernel = new StandardKernel(new MainModule());\n\n        // This one line makes DI magically work with WinForms\n        // It allows us to recursively inject controls inside forms or other controls\n        kernel.Components.Add\u003cIActivationStrategy, WindowsFormsStrategy\u003e();\n\n        // Get MainForm with injected dependencies and run application\n        var mainForm = kernel.Get\u003cMainForm\u003e();\n        Application.Run(mainForm);\n    }\n}\n```\n\n##### \"Models\" folder\n\nNothing special. We place Models of MVP pattern here. In our case we have a tiny `IUser` interface and its implementation `DefaultUser`.\n\n##### \"Views\" folder\n\nHere we place our Views of MVP pattern. Let's take a closer look at code of `IMainView` interface and its implementation `MainForm`.\n\nFirst of all, we have to inject presenter into our form. \nWe use property injection because WinForms use a style when components have default constructor and everything else is initialized via properties after the object is created.\n\nView instance is the one that initialized presenter by setting its View property to reference the View.\n\n```csharp\nprivate IMainPresenter? presenter;\n\n[Inject]\npublic IMainPresenter? Presenter\n{\n    get =\u003e presenter;\n    set\n    {\n        // View can have only single presenter associated with it\n        if (presenter != null)\n            presenter.View = null;\n\n        if (value == null)\n            throw new ArgumentNullException(nameof(value));\n\n        // Set and initialize a new presenter\n        presenter = value;\n        presenter.View = this;\n    }\n}\n```\n\nBecause of cyclic reference between View and Presenter we resolve it during View disposure by runtime otherwise garbage collector won't be able to collect disposed view and its presenter. That's why we add a handler to `Disposed` event to remove cyclic reference by nulling Presenter's `View` property.\n\n```csharp\npublic MainForm()\n{\n    InitializeComponent();\n    Disposed += (sender, args) =\u003e\n    {\n        // Resolve cyclic reference to let GC collect the objects\n        if (presenter != null)\n        {\n            presenter.View = null;\n            presenter = null;\n        }\n    };\n}\n```\n\n##### \"Presenters\" folder\n\nThis folder contains Presenters of MVP pattern. Compared to Views, Presenters don't have much going on in terms of injections and cleaning up. \n\nPresenter should have a reference to its View so that it can interact with it by setting View properties.\n\n```csharp\npublic IMainView? View\n{\n    get; set;\n}\n\npublic async Task GetUsers()\n{\n    View!.Users = await userService.GetUsers();\n}\n```\n\n##### \"Services\" folder\n\nThis one contains your services that you may use in Presenters to get users from API, database or something else.\nIn our case we have a dummy service that always returns hardcoded users.\n\n```csharp\npublic class DefaultUserService : IUserService\n{\n    public Task\u003cIUser[]\u003e GetUsers()\n    {\n        return Task.FromResult(new IUser[]\n        {\n            new DefaultUser { Name = \"Name1\", Age = 10 },\n            new DefaultUser { Name = \"Name2\", Age = 20 },\n            new DefaultUser { Name = \"Name3\", Age = 30 },\n            new DefaultUser { Name = \"Name4\", Age = 40 },\n            new DefaultUser { Name = \"Name5\", Age = 50 }\n        });\n    }\n}\n```\n\n##### \"Factories\" folder\n\nWe are not interested in this folder itself but in one interface that in contains.\n`IViewFactory` is an interface that is bound by Ninject in `MainModule` in a special way.\n\nInterface itself doesn't have much.\n\n```csharp\n// Allows to create us injected forms and control dynamically at runtime\npublic interface IViewFactory\n{\n    ISomeRandomView CreateSomeRandomView();\n}\n```\n\nThe part we are interested in the most is binding in `MainModule`. This binding make it possible to create injected forms and controls at runtime dynamically.\nIt may not sound that big, but if you try to create a form that has injected dependencies at runtime you'll run into a few issues. One of which is how do you do it? How do you even inject its dependencies?\n\nProbably it would look like something like this. You'll create or pass all dependencies manually. These were only a few dependencies and there can be much much more of them in a real application. The code will be hard to maintain and it will look ugly.\n\n```csharp\nprivate void btnOpenSomeRandomView_Click(object sender, EventArgs e)\n{\n    var form = new SomeRandomForm();\n    var presenter = new DefaultSomeRandomPresenter();\n    presenter.Service = new DefaultUserService();\n    form.Presenter = presenter;\n    form.Show();\n}\n```\n\n`IViewFactory` allows us to inject all dependencies automatically. Ninject injects them and we have a very clean code.\n\n```csharp\n[Inject]\npublic IViewFactory? ViewFactory\n{\n    get; set;\n}\n\nprivate void btnOpenSomeRandomView_Click(object sender, EventArgs e)\n{\n    // This is how you create an injected form or control during runtime. Amazing!\n    var form = (Form)ViewFactory!.CreateSomeRandomView();\n    form.Show();\n}\n```\n\n`MainModule` binds `IViewFactory` as a Ninject factory that makes all the magic happen.\n\n```csharp\npublic class MainModule : NinjectModule\n{\n    public override void Load()\n    {\n        ...\n        BindFactories();\n    }\n    \n    ...\n\n    private void BindFactories()\n    {\n        // A neat Ninject extension that in our case allows us to create injected forms/control dynamically\n        // https://github.com/ninject/Ninject.Extensions.Factory/wiki\n        Bind\u003cIViewFactory\u003e().ToFactory();\n    }\n}\n```\n\n#### WinFormsMvpNinject.Tests\n\nThis project contains a single test class `MemoryLeaksTest` that checks if cyclic reference issue previously explained is resolved correctly.\n\n```csharp\n[Test]\npublic void MainForm_Presenter_IsNull_OnDispose()\n{\n    var mainForm = kernel.Get\u003cMainForm\u003e();\n\n    // Make sure that presenter was curretly injected\n    Assert.IsNotNull(mainForm.Presenter);\n\n    // Dispose form as if it was closed by the user or something\n    mainForm.Dispose();\n\n    // Make sure that presenter was curretly set to null by Disposed event handler\n    // This means that mainForm will be collected by GC because cyclic references are resolved\n    Assert.IsNull(mainForm.Presenter);\n}\n```\n\n## Built With\n\n* [Ninject](https://github.com/ninject/Ninject) - Dependency Injector\n* [Ninject.Extensions.Factory](https://github.com/ninject/Ninject.Extensions.Factory) - Ninject extension that allows us to create injected WinForms forms and controls dynamically\n\n## Contributing\n\nI would be really pleased if someone could help me to resolve boilerplate issue with setting and cleaning Presenter.\n\nThese two boilerplate pieces will be all over your codebase in every View. It's not that big of a problem but it would be nice not to have them.\n\n```csharp\n[Inject]\npublic IMainPresenter? Presenter\n{\n    get =\u003e presenter;\n    set\n    {\n        // View can have only single presenter associated with it\n        if (presenter != null)\n            presenter.View = null;\n\n        if (value == null)\n            throw new ArgumentNullException(nameof(value));\n\n        // Set and initialize a new presenter\n        presenter = value;\n        presenter.View = this;\n    }\n}\n```\n\n```csharp\npublic MainForm()\n{\n    InitializeComponent();\n    Disposed += (sender, args) =\u003e\n    {\n        // Resolve cyclic reference to let GC collect the objects\n        if (presenter != null)\n        {\n            presenter.View = null;\n            presenter = null;\n        }\n    };\n}\n```\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details\n\n## Acknowledgments\n\n* Used [this nice GitHub repo](https://github.com/mrts/winforms-mvp) to implement MVP pattern in this pattern\n* [This StackOverflow answer](https://stackoverflow.com/a/33928388) allows to use Ninject with WinForms\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fipasechnikov%2Fwinforms-mvp-ninject","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fipasechnikov%2Fwinforms-mvp-ninject","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fipasechnikov%2Fwinforms-mvp-ninject/lists"}