From 0c2f0f787f84a46980f2096a912559d4588d9c9b Mon Sep 17 00:00:00 2001 From: Markus Ewald Date: Thu, 19 Jun 2025 18:32:22 +0200 Subject: [PATCH] Pegged latest versions of all involved projects; split single NuGet package recipe into three, one core and one each for Ninject and Microsoft dependency injection --- .gitignore | 4 +- .gitmodules | 5 ++ Nuclex.Foundation.DependencyInjection.nuspec | 50 +++++++++++++ Nuclex.Foundation.Ninject.nuspec | 50 +++++++++++++ ...package.nuspec => Nuclex.Foundation.nuspec | 34 ++++----- Nuclex.Support | 2 +- Nuclex.Windows.Forms | 2 +- Nuclex.Windows.Forms.DependencyInjection | 1 + Nuclex.Windows.Forms.Ninject | 2 +- ReadMe.md | 70 +++++++++++++------ foundation-package.sln | 6 ++ nuget-pack.cmd | 5 ++ 12 files changed, 184 insertions(+), 47 deletions(-) create mode 100644 Nuclex.Foundation.DependencyInjection.nuspec create mode 100644 Nuclex.Foundation.Ninject.nuspec rename foundation-package.nuspec => Nuclex.Foundation.nuspec (55%) create mode 160000 Nuclex.Windows.Forms.DependencyInjection create mode 100644 nuget-pack.cmd diff --git a/.gitignore b/.gitignore index e76a874..52701a6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ -NuclexSupportConfig.cmake +FoundationPackageConfig.cmake + +third-party # Visual Studio Codium .vscode/* diff --git a/.gitmodules b/.gitmodules index 755403c..5b94603 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,8 @@ url = ../../nuclex-shared-dotnet/Nuclex.Windows.Forms.Ninject.git branch = main update = rebase +[submodule "Nuclex.Windows.Forms.DependencyInjection"] + path = Nuclex.Windows.Forms.DependencyInjection + url = ../../nuclex-shared-dotnet/Nuclex.Windows.Forms.DependencyInjection.git + branch = main + update = rebase diff --git a/Nuclex.Foundation.DependencyInjection.nuspec b/Nuclex.Foundation.DependencyInjection.nuspec new file mode 100644 index 0000000..7da305d --- /dev/null +++ b/Nuclex.Foundation.DependencyInjection.nuspec @@ -0,0 +1,50 @@ + + + + Nuclex.Foundation.DependencyInjection + 1.2.0 + Dependency injection bindings for the Nuclex Foundation Libraries + Markus Ewald + + false + Apache-2.0 + ReadMe.md + https://github.com/orgs/nuclex-shared-dotnet/repositories + Bindings to use Microsoft's (and compatible) dependency injector in Microsoft.Extensions.DependencyInjection(.Asbtraction) for view models with Nuclex.Windows.Forms + en-US + mvvm,util,ninject,dependency-injection + nuclex-logo.png + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Nuclex.Foundation.Ninject.nuspec b/Nuclex.Foundation.Ninject.nuspec new file mode 100644 index 0000000..4307be9 --- /dev/null +++ b/Nuclex.Foundation.Ninject.nuspec @@ -0,0 +1,50 @@ + + + + Nuclex.Foundation.Ninject + 1.2.0 + Ninject bindings for the Nuclex Foundation Libraries + Markus Ewald + + false + Apache-2.0 + ReadMe.md + https://github.com/orgs/nuclex-shared-dotnet/repositories + Bindings to use Ninject for dependency injection in view model with Nuclex.Windows.Forms + en-US + mvvm,util,ninject,dependency-injection + nuclex-logo.png + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/foundation-package.nuspec b/Nuclex.Foundation.nuspec similarity index 55% rename from foundation-package.nuspec rename to Nuclex.Foundation.nuspec index daec482..15c40b0 100644 --- a/foundation-package.nuspec +++ b/Nuclex.Foundation.nuspec @@ -2,31 +2,29 @@ Nuclex.Foundation - 1.1.0 + 1.2.0 Nuclex Foundation Libraries Markus Ewald false Apache-2.0 + https://licenses.nuget.org/Apache-2.0 ReadMe.md - https://github.com/nuclex-shared-dotnet/Nuclex.Support + https://github.com/orgs/nuclex-shared-dotnet/repositories Set of basic libraries offering tools for MVVM-based UIs, storage of settings, license keys, collections and observer/subscriber pattern helpers en-US mvvm,util nuclex-logo.png - + - - - - - - + + + - + @@ -45,17 +43,13 @@ - - - - - - - - - - + + + + + + \ No newline at end of file diff --git a/Nuclex.Support b/Nuclex.Support index e818fae..5fb433a 160000 --- a/Nuclex.Support +++ b/Nuclex.Support @@ -1 +1 @@ -Subproject commit e818fae81012b0a819abcf2180b90eee1d998fb7 +Subproject commit 5fb433ad983e58725e0d25faa8968a75b3dfd178 diff --git a/Nuclex.Windows.Forms b/Nuclex.Windows.Forms index 563d45f..d057f70 160000 --- a/Nuclex.Windows.Forms +++ b/Nuclex.Windows.Forms @@ -1 +1 @@ -Subproject commit 563d45f9f2f3b666586eb0862088ac3e00566389 +Subproject commit d057f704497b63faa5e57c6330afb436a0d9708c diff --git a/Nuclex.Windows.Forms.DependencyInjection b/Nuclex.Windows.Forms.DependencyInjection new file mode 160000 index 0000000..573b7ab --- /dev/null +++ b/Nuclex.Windows.Forms.DependencyInjection @@ -0,0 +1 @@ +Subproject commit 573b7ab93d25034a649bdf401f5439ec15c165f5 diff --git a/Nuclex.Windows.Forms.Ninject b/Nuclex.Windows.Forms.Ninject index cb4e398..d3e1a3e 160000 --- a/Nuclex.Windows.Forms.Ninject +++ b/Nuclex.Windows.Forms.Ninject @@ -1 +1 @@ -Subproject commit cb4e39895666e467dec8b3b92abdd73127687385 +Subproject commit d3e1a3e24e7ff6615996ec9236495b6d1275de5c diff --git a/ReadMe.md b/ReadMe.md index eae9126..af68c28 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -35,7 +35,7 @@ void saveSettingsToIni() { var iniStore = new ConfigurationFileStore(); saveSettings(iniStore); - using(var writer = new StreamWriteR("awesome-app.ini")) { + using(var writer = new StreamWriter("awesome-app.ini")) { iniStore.Save(writer); writer.Flush() } @@ -78,9 +78,8 @@ static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - using(var windowManager = new WindowManager()) { - Application.Run(windowManager.OpenRoot()); - } + var windowManager = new WindowManager(); + Application.Run(windowManager.OpenRoot()); } ``` @@ -99,8 +98,8 @@ will be assigned to its `DataContext` property, establishing the View/ViewModel relationship. -Adding an IoC Container (Ninject) to the MVVM Example ------------------------------------------------------ +Adding Dependency Injection to the MVVM Example +----------------------------------------------- In the previous example, the view and its view model were constructed using `Activator.CreateInstance()` - a method provided by .NET that creates a new @@ -111,30 +110,55 @@ to provide the ViewModel with the data it is supposed to be an adapter for. You can achieve that by constructing the ViewModel yourself and passing it to the `WindowManager.OpenRoot()` or `WindowManager.ShowModal()` methods. -A much better approach is to use a dependency injector - an IoC container with -automatic constructor parameter injection. My favorite one is Ninject (due to -its neat setup with a fluent interface), but you can use any container you -wish, simply by inheriting your own `WindowManager` class: +A much better approach is to use a dependency injector that automatically +wires up your view models to app-internal services, repositories and managers +to which the view models and mediate. + +Let's do the full program with the host application builder from Microsoft's +`Microsoft.Extensions.Hosting` assembly which would also give you logging and +hosted services in addition its dependency injector: ```csharp -public class NinjectWindowManager : WindowManager { +[STAThread] +static void Main() { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); - public NinjectWindowManager(IKernel kernel, IAutoBinder autoBinder = null) : - base(autoBinder) { - this.kernel = kernel; + HostApplicationBuilder builder = Host.CreateApplicationBuilder( + new HostApplicationBuilderSettings() { + DisableDefaults = true, + ContentRootPath = "", + ApplicationName = "Example" + } + ); + + // These are extension methods in Nuclex.Windows.Forms.DependencyInjection + builder.Services.AddMvvm(); // Window Manager + builder.Services.AddCommonDialogs(); // Message boxes, file dialogs, etc. + + // View models and dialogs must be explicitly registered by default. + // This requirement can be relaxed. + builder.Services.AddTransient(); + builder.Services.AddTransient(); + + // Create the host (in which all application services and scopes will + // be managed) and run the main window. + using(IHost host = builder.Build()) { + var windowManager = host.Services.GetRequiredService(); + Application.Run(windowManager.OpenRoot()); } - - protected override object CreateInstance(Type type) { - return this.kernel.Get(type); - } - - private IKernel kernel; } ``` -Your `NinjectWindowManager` will now use `IKernel.Get()` to construct its -ViewModels, allowing their constructors to require any services and instances -you have set up in your Ninject kernel. +Now view models can access any registered services. By default, each view and +view model pair lives inside of an implicit 'scope', meaning that services +added via `builder.Services.AddScoped()` will have unique instances per open +dialog or window. + +This is very useful if your dialogs perform background processing via threads +(i.e. using `ThreadedViewModel`) or tasks where a shared database connection, +for example, would lead to trouble when it is asked to run another query while +a data reader is still open. ```csharp class MainViewModel { diff --git a/foundation-package.sln b/foundation-package.sln index 463c9d7..fa89d38 100644 --- a/foundation-package.sln +++ b/foundation-package.sln @@ -15,6 +15,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Nuclex.Windows.Forms.Ninjec EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Unit Tests", "Unit Tests", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Nuclex.Windows.Forms.DependencyInjection (net-4.6)(net-8.0)", "Nuclex.Windows.Forms.DependencyInjection\Nuclex.Windows.Forms.DependencyInjection (net-4.6)(net-8.0).csproj", "{3D7D6CA0-A130-FD54-B772-B4B48847E682}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,6 +43,10 @@ Global {98B1CB8B-9202-4008-9254-D0BE0B2D38AF}.Debug|Any CPU.Build.0 = Debug|Any CPU {98B1CB8B-9202-4008-9254-D0BE0B2D38AF}.Release|Any CPU.ActiveCfg = Release|Any CPU {98B1CB8B-9202-4008-9254-D0BE0B2D38AF}.Release|Any CPU.Build.0 = Release|Any CPU + {3D7D6CA0-A130-FD54-B772-B4B48847E682}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3D7D6CA0-A130-FD54-B772-B4B48847E682}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D7D6CA0-A130-FD54-B772-B4B48847E682}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3D7D6CA0-A130-FD54-B772-B4B48847E682}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/nuget-pack.cmd b/nuget-pack.cmd new file mode 100644 index 0000000..24d6c49 --- /dev/null +++ b/nuget-pack.cmd @@ -0,0 +1,5 @@ +@ECHO OFF + +nuget pack Nuclex.Foundation.nuspec -Version 1.2.0 -Properties Configuration=Release +nuget pack Nuclex.Foundation.Ninject.nuspec -Version 1.2.0 -Properties Configuration=Release +nuget pack Nuclex.Foundation.DependencyInjection.nuspec -Version 1.2.0 -Properties Configuration=Release