Nuclex.Foundation.Ninject (1.2.1)

Published 2025-06-24 10:54:26 +00:00 by m.ewald in nuclex-shared-dotnet/foundation-package

Installation

dotnet nuget add source --name nuclex-shared-dotnet --username your_username --password your_token 
dotnet add package --source nuclex-shared-dotnet --version 1.2.1 Nuclex.Foundation.Ninject

About this package

Bindings to use Ninject for dependency injection in view model with Nuclex.Windows.Forms

Nuclex Foundation Libraries

A set of clean and carefully designed utility classes for .NET.

For specific documentation, see the individual projects in this package.

Settings (.ini / Windows Registry)

Many applications have to store their settings in an external file or, for pure Windows applications, in the registry. This can be tedious and difficult to unit test, too. Nuclex.Support provides an autonomous ini parser (which works cross-platform and does not rely on GetPrivateProfileString).

Furthermore, it uses an interface to provide the same functionality for the Registry and in-memory settings. This lets you switch between storing your settings in the registry, in an .ini file or constructing a settings container in memory to appropriately unit-test your code with mock data.

static readonly string BasicCategoryName = "Basic";
static readonly string HintsCategoryName = "Hints";

void saveSettings(ISettingStore settingsStore) {
  settingsStore.Set(BasicCategoryName, "AskSaveOnQuit", this.askSaveOnQuit);
  settingsStore.Set(BasicCategoryName, "ActivePanel", this.activePanelIndex);
  settingsStore.Set(HintsCategoryName, "ShowNameHint", this.showNameHint);
  // ...
}

void saveSettingsToIni() {
  var iniStore = new ConfigurationFileStore();
  saveSettings(iniStore);

  using(var writer = new StreamWriter("awesome-app.ini")) {
    iniStore.Save(writer);
    writer.Flush()
  }
}

void saveSettingsToRegistry() {
  using(
    var registryStore = new WindowsRegistryStore(
      RegistryHive.HKCU, "AwesomeApplication"
    )
  ) {
    saveSettings(registryStore);
  }
}

MVVM with Nuclex.Windows.Forms

This library implements the MVVM pattern through its WindowManager class:

The WindowManager keeps track of all open windows and their view models, so your basic Main() method, which normally looks like this:

[STAThread]
static void Main() {
  Application.EnableVisualStyles();
  Application.SetCompatibleTextRenderingDefault(false);
  Application.Run(new MainForm());
}

Now becomes this:

[STAThread]
static void Main() {
  Application.EnableVisualStyles();
  Application.SetCompatibleTextRenderingDefault(false);

  var windowManager = new WindowManager();
  Application.Run(windowManager.OpenRoot<MainViewModel>());
}

As you can see, we no longer mention the MainForm by name, instead we ask the WindowManager to construct a new MainViewModel and also create a view that displays it.

It does so by using a "convention over configuration" approach, meaning it assumes that if you request a view for FlurgleSettingsViewModel, it will look for a view named FlurgleSettingsView, FlurgleSettingsForm, FlurgeSettingsWindow or FlurgleSettingsDialog class and try to construct an instance of that class.

Furthermore, if that class implements the IView interface, the view model will be assigned to its DataContext property, establishing the View/ViewModel relationship.

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 instance via a type's default constructor.

Most of the time, ViewModels have constructor parameters, however. For example 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 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:

[STAThread]
static void Main() {
  Application.EnableVisualStyles();
  Application.SetCompatibleTextRenderingDefault(false);

  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<MainViewModel>();
  builder.Services.AddTransient<MainForm>();

  // 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<IWindowManager>();
    Application.Run(windowManager.OpenRoot<MainViewModel>());
  }
}

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<T>() 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.

class MainViewModel {

  public MainViewModel(IMyService myService, IMySettings mySettings) {
  	// ...
  }

}

Observable Base Class for Data Binding and ORMs

.NET provides the INotifyPropertyChanged interface for objects to expose an event that reports when a property of the object has changed. This is used by data binding UI controls and some ORMs to detect when an object has been changed and the UI or database need to be updated.

It is a bit tedious to implement, so here's a base class to make it much more pleasant to use:

class CreateUserViewModel : Observable {

  public string FirstName {
    get { return this.firstName; }
    set {
      if(value != this.firstName) {
        this.firstName = value;
        OnPropertyChanged(nameof(FirstName));
      }
    }
  }

  private string firstName;

}

There's an extension method for the consuming side, too, with proper handling of wildcard change notifications that are often overlooked:

CreateUserViewModel ViewModel { get; set; } 

void onPropertyChanged(object sender, PropertyChangedEventArgs arguments) {
  if(arguments.AreAffecting(nameof(ViewModel.FirstName))) {
    this.firstNameLine.Text = ViewModel.FirstName;
  }
}

Cloning Objects

Whether you use the prototye design patten on complex objects or have another reason, sometimes a deep clone of an object tree is needed. This library provides three complete solutions to cloning objects in .NET:

  • The SerializationCloner. It uses .NET's BinarySerializer in a way that will serialize your object tree regardless of whether your objects have the Serializable attribute or not. This is the slowest, least efficient object cloner, but it relies on built-in .NET classes only.

  • The ReflectionCloner uses .NET's reflection capabilities (that means interrogating an object what fields and properties it has) to create complete clones of an object, including any arrays and referenced objects. This serializer has no setup time and has pretty decent performance.

  • The ExpressionTreeCloner uses Linq expression trees to generate tailored cloning code for your classes at runtime. This method of cloning has a setup time (meaning it takes longer the first time it is confronted with a new class), but from the second clone onwards, is much faster than the others.

All three object cloners can create shallow clones (meaning any references to other object will be kept without copying the referenced objects, too) and deep clones meaning any refeferenced objects (and their referenced objects) will be cloned as well. Careful, this means event subscribers, such a forms and unexpected hangers-on will be cloned, too.

Furthermore, all three object cloners can create property-based clones (where only those settings exposed via properties are cloned), which may skip the non-exposed parts of an object, as well as field-based clones which replicate all the data of a class - any private field and hidden state.

class Example {
  public Example(Example child = null) {
    Child = child;
  }
  public Example Child { get; private set; }
}

class Test {
  public static void CloneSomething() {
    var test = new Example(new Example());

    var reflectionCloner = new ReflectionCloner();
    var clone = reflectionCloner.DeepFieldClone(test);

    // Clone is now a complete copy of test, including the child object
  }
}

Dependencies

ID Version Target Framework
Nuclex.Foundation [1.2.1, 2.0.0) .NETFramework4.6.2
Ninject [3.3.6, 4.0.0) .NETFramework4.6.2
Nuclex.Foundation [1.2.1, 2.0.0) net8.0-windows7.0
Ninject [3.3.6, 4.0.0) net8.0-windows7.0
Details
NuGet
2025-06-24 10:54:26 +00:00
0
Markus Ewald
50 KiB
Assets (2)
Versions (2) View all
1.2.1 2025-06-24
1.2.0 2025-06-19