dotnet icon indicating copy to clipboard operation
dotnet copied to clipboard

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

Open nicolaihenriksen opened this issue 2 years ago • 2 comments

The text below is copied from my repository 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 by @keboo.

This allows us to extract the "top-most" view from the DI container (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

<Window x:Class="SampleApp.MainWindow" ...>
  <local:MyUserControl />
</Window>

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)

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:

[InjectDependenciesFromDefaultConstructor]  // This instructs the source generator to generate an empty constructor
public class MyUserControl : UserControl
...
using IHost host = CreateHostBuilder(args)
                     .UseSourceGeneratedDefaultConstructors()   // This registers the static access to the IServiceProvider
                     .Build();

nicolaihenriksen avatar Aug 08 '23 13:08 nicolaihenriksen

yes, that's easy to use, now , we must define a Func or Action Dependency Property in the user control,
inject the the IService in the window of control's parents , assign the Action or Func which to use IService. then binding it to the xaml. it's too complex.

ximenchuifeng avatar Jul 31 '24 01:07 ximenchuifeng

other solution, use the inject tool support property inject or method inject

ximenchuifeng avatar Jul 31 '24 01:07 ximenchuifeng