Bent Rasmussen
Bent Rasmussen

Reputation: 5702

Using `Application.LoadComponent` with dependency injection

I have a XAML file in an F# project. The XAML file is built as a resource (not a BAML-compiled page).

In order to dynamically load the page, I use the Application.LoadComponent method with an appropriate URI to find the resource. This works fine (albeit slowly).

Also, in order to specialize the component (e.g. a Window), I create a custom MainWindow class which inherits from Window. This also works fine.

type MainWindow() =
    inherit Window()

The problem now arises when introducing dependency injection via Microsoft.Extensions.DependencyInjection. The issue is that LoadComponent does not know how to use my custom-built DI container to inject constructor arguments for MainWindow (e.g. it could require a FooService):

type MainWindow(foo: FooService) =
    inherit Window()

Indeed, WPF knows nothing of this external service provider, since I have not told it about it.

Is there some kind of WPF magic one can use to hook into the LoadComponent machinery and ensure that dependency injection works?

This is not an issue in C# where BAML compilation ensures there is a cohesive compiled class that embodies both the XAML and the code-behind, and the programmer need not be concerned with manually loading the XAML.

In F# the story is different, and I do not know how to introduce dependency injection into the WPF machinery. Since Application.LoadComponent does not have any overloads to supply a service provider, I was wondering whether there is some other mechanism to do this.

My fallback approach is to grab a global reference to the service provider in the MainWindow class initialization code, a la:

type MainWindow() =

    inherit Window()

    let servicerProvider = AppHost.Services
    let foo = serviceProvider.GetRequiredService<FooService>()

The whole DI-business seems a bit dicey for this use-case, especially for child windows, but for now I am pursuing this avenue, since the framework I have started using, is also using it.

Upvotes: 2

Views: 158

Answers (1)

BionicCode
BionicCode

Reputation: 28948

The point is that the whole XAML engine is built around the constraint that all types that are instantiated by the engine must declare a default constructor as this is the only constructor that the engine will invoke. Without any type information how can the engine know how to construct a type to satisfy the constructor dependencies? XAMl is still a markup language.

Instead, types used in XAML are usually initialized via the dependency property infrastructure e.g., a property default or property inheritance, and configured using data binding or by defining a Style or by setting properties locally.

"My fallback approach is to grab a global reference to the service provider in the MainWindow class initialization code"

That's a terrible idea and is not Dependency Injection. That's the Service Locator, the infamous anti-pattern that causes more harm than it solves problems and is usually avoidable. Using Singleton or Service Locator design patterns when applying the Dependency Injection pattern is totally pointless and adds absolutely redundant complexity as they effectively nullify the efficiency of Dependency Injection. Dependency Injection is usually used to eliminate both of those anti-patterns.
Making everything static violates so many good and important principles and is guaranteed to lead to smelly code. Because the referenced variables are static, following your pattern will make your complete application smell.

Whenever you feel like sharing the IoC container, don't do it. Instead, use a factory. Always.
Abstract Factory is a powerful creational design pattern that solves many problems related to type instantiation.

The recommended solution is:

  1. Wrap the Application.LoadComponent call for a specific type into a dedicated factory.

  2. You define the dependencies of the XAML type as dependencies of this factory.

  3. Then you register the factory with the IoC container like any other type so that all dependencies are resolved and provided by the IoC container. If some of these dependencies are not shared instances, the factory itself must receive dedicated factories in order to create instances of the XAML type's transient dependencies dynamically.

  4. Next provide the dependencies to the instance which is returned from Application.LoadComponent by assigning them either to

    • a) a custom InitializeComponent method, declared by the XAML type, and that defines all the dependencies that would normally be defined by the constructor (recommended)
    • b) or to public properties.

    You should not declare properties as public only for this purpose. If those properties must be hidden, then use variant a).
    a) is the most graceful solution as it doesn't require the factory to know any details about the type (the fewer the better). The factory simply has to provide the argument list for the initialization method.

  5. Add the properly constructed type to the visual tree and use it.

Another solution could be to inject all dependencies into the MainWindow and make it expose them via read-only dependency properties and then let your UI elements bind to those properties to get their dependencies. This may work for small scenarios, especially where the dependency is a shared instance, but this usually violates common data hiding principles.

For your particular case a factory for each XAML type, that wraps the Application.LoadComponent call, is the cleanest way and most common way.

"The whole DI-business seems a bit dicey for this use-case, especially for child windows"

Now you know that you can solve all this kind of problems by injecting factories for the type where creation is problematic.
For example, when the parent Window receives the factory to create the child Window (e.g. a dialog) then all the dependencies and configuration details are hidden and encapsulated in this factory. The parent can create types of the child without having itself to depend on the child's dependencies only to delegate them.
Abstract Factory is the omni present companion of the Dependency Injection design pattern.

Upvotes: 2

Related Questions