CommunityToolkit / CommunityToolkit/dotnet

[Feature Request] Enabling DI for WPF UserControl (or any class for that matter)

Open
#746 2 comments 11 reactions 0 assignees View on GitHub
Dominant language
C#
Stars
3.8k
Forks
400
PR merge metrics
No merged PRs in 30d

Description

The text below is copied from [my repository](https://github.com/nicolaihenriksen/CommunityToolkit.DependencyInjection) README.md where I have built a proof-of-concept implementation of this feature.

---

The problem described below is probably the most compelling use case for this feature, however I do see other use cases for it as well.

Consider a simple WPF application using the `GenericHost` to enable dependency injection following the standard .NET scheme.
The approach is nicely described in [this video](https://www.youtube.com/watch?v=j3pl2tkBM1A&t=6s) by [@keboo](https://github.com/Keboo).

This allows us to [extract the "top-most" view from the DI container](https://github.com/nicolaihenriksen/CommunityToolkit.DependencyInjection/blob/26eda98270d5d0393ea3a07613b6bcf165fd5b16/SampleApp/App.xaml.cs#L40) (and thus use a non-default constructor). However, nested controls inside this view are problematic, because they are "instantiated by the XAML parser" and thus require an empty default constructor.

Consider a "top-most" view called `MainWindow` using a `UserControl` (or a custom control):

**MainWindow.xaml**
```xaml

```

We now want the nested view (i.e. `MyUserControl`) to have a non-default constructor so we can use dependency injection:

**MyUserControl.xaml.cs** (code-behind)
```csharp
public class MyUserControl : UserControl
{
private readonly IMessenger _messenger;

public MyUserControl(IMessenger messenger) // This does not work out of the box - default ctor is needed!
{
_messenger = messenger;
}
}
```

As mentioned, this will not work out of the box, because there needs to be an empty default constructor.

This is just one example of the issue. I could easily imagine other scenarios where this could be useful. For example a "plugin architecture" scenario where plugins are instantiated via reflection using the default constructor.

## Proposed solution
The general idea is quite simple:
> Use an incremental source generator to create an empty default constructor which in turn invokes the DI-enabled constructor by looking up the dependencies in the DI container.

For this to work, basically 2 things are needed:
1. An incremental source generator producing the empty default constructor for (partial) types decorated with a marker attribute.
2. A means of getting (static) access to the `IServiceProvider` from the generated constructors in order to lookup the required services.

#### Desired usage
I want the usage of this to be as simple as possible. Hopefully only 2 steps are needed:
1. Decorate the type (e.g. `MyUserControl`) which should be "DI-enabled" with a marker attribute.
2. Inject the "necessary stuff" into the `IHostBuilder` using a simple extension method.

Ideally something like this:
```csharp
[InjectDependenciesFromDefaultConstructor] // This instructs the source generator to generate an empty constructor
public class MyUserControl : UserControl
...
```

```csharp
using IHost host = CreateHostBuilder(args)
.UseSourceGeneratedDefaultConstructors() // This registers the static access to the IServiceProvider
.Build();
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.