CommunityToolkit / dotnet

.NET Community Toolkit is a collection of helpers and APIs that work for all .NET developers and are agnostic of any specific UI platform. The toolkit is maintained and published by Microsoft, and part of the .NET Foundation.
https://docs.microsoft.com/dotnet/communitytoolkit/?WT.mc_id=dotnet-0000-bramin
Other
3.06k stars 300 forks source link

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

Open nicolaihenriksen opened 1 year ago

nicolaihenriksen commented 1 year ago

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();
ximenchuifeng commented 3 months ago

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 commented 3 months ago

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