CommunityToolkit / CommunityToolkit/dotnet
[Feature Request] Enabling DI for WPF UserControl (or any class for that matter)
- 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
Assessment
This issue has not been assessed yet.