dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.28k stars 1.76k forks source link

Dependency Injection for Custom Controls #4538

Open dallas1287 opened 2 years ago

dallas1287 commented 2 years ago

Description

I was playing with the Dependency Injection in Preview 12 and noticed that if I have a custom control that I want to inject a view model into, it's not currently possible. When I place the control inside another ContentPage the xaml warns that the control doesn't have a default constructor. This may just be a limitation and there are definitely workarounds but I figured I would put it on the radar because it'd be a nice thing to button up on what is a great feature.

Public API Changes

Not sure there are public API changes, I'm not totally familiar with how the xaml deals with constructors, but if it could determine if a constructor has services that have been registered with the service collection and use that constructor without having to interact with the code behind, that'd be pretty nice.

Intended Use-Case

Custom Controls that dependency inject view models which themselves dependency inject services.

tripjump commented 2 years ago

I have used dependency injection in .net MAUI to inject a state management object in a custom control. For a viewmodel it should look like this: CustomControl.xaml file: <ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui" ... x:Name="this">

CustomControl.xaml.cs file: public static readonly BindableProperty ViewModelProperty = BindableProperty.Create(nameof(ViewModel), typeof(IViewModel), typeof(CustomControl));

public IViewModel ViewModel
{
    get => (ViewModel)GetValue(CustomControl.ViewModelProperty);
    set => SetValue(CustomControl.ViewModelProperty, value);
}

MainPage.xaml : <ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui" ... x:Name="this"> .....

**Injection:** _MainPage.xaml.cs_: public MainPage(IViewModel viewModel) { InitializeComponent(); BindingContext = viewModel; } _MauiProgram.cs_: builder.Services.AddSingleton(); builder.Services.AddSingleton();
jthornjd commented 2 years ago

Just pointing out that this particular example is just passing the MainPage ViewModel along to the custom control. But I could see where the MainPage ViewModel injected the Control ViewModel (which is independent from the MainPage ViewModel) as a property, then the Control would bind to that ViewModel.

jthornjd commented 2 years ago

All of this is to say that I know there are workarounds and this is definitely icing on the cake and not the batter, but it would be pretty awesome if the dependency service and the xaml could sync up and recognize that a custom control is looking for a dependency to be injected and if the dependency service has something valid registered go ahead and use that constructor directly in the xaml without all the rigmarole of setting up a new dependency property. Still love this feature regardless.

mikeedwards83 commented 2 years ago

100% support for this. Without this, it makes it hard work to handle complex control trees

ddobric commented 1 year ago

Why should that not be supported? It is a must-feature.

zemdei commented 1 year ago

Absolutely needed! Please make this a priority.

zemdei commented 1 year ago

A simple way to work around this is by assigning a BindingContext to the bound model in the parent contentpage like this BindingContext="{Binding}". Say you have a ContentPage with a CollectionView inside and the DataTemplate of the CollectionView is your custom control. You can assign the context to your custom control like this:

<CollectionView Grid.Column="1" ItemsSource="{Binding RightSideDayCards}">
                    <CollectionView.ItemTemplate>
                        <DataTemplate>
                            <controls:CalendarDayCard  BindingContext="{Binding}"></controls:CalendarDayCard>
                        </DataTemplate>
                    </CollectionView.ItemTemplate>
                </CollectionView>

Then simply be sure to not override that context anywhere in the xaml of your custom control.

FcAYH commented 1 year ago

I was bothered by this problem for a long time, only to find out that is was because MAUI did not yet support DI for custom controls. I think this feature is absolutely needed as well!

ToolmakerSteve commented 1 year ago

Whle the original question is about BindingContext, that is not the only need for DI.

Consider a custom control that relies on some service, MyService. The containing page has no reason to know about this.

Consider:

public partial class MyView : ContentView
{
    public MyView(MyService myService)
    {
        InitializeComponent();
                ...
    }
}

If attempt to use in XAML:

... xmlns:local="clr-namespace:MyApp" ...

`<local:MyView />

Get Error XLS0507 Type 'MyView' is not usable as an object element because it is not public or does not define a public parameterless constructor or a type converter.

And of course if you add a parameterless constructor, then GetService will use that one instead of the desired one, ruining the use of GetService elsewhere.


NOTE: the C# GetService version works:

public class MyPage : ContentPage
{
  public MyPage()
  {
      Content = App.Services.GetService<MyView>();   // App.Services property defined during App construction.
  }
}

Ideally, XAML would recognize that MyView is injectable, and perform GetService.
OR at least some way to represent GetService call in XAML.


NOTE: A work-around is to give up on DI injection of parameter, and call GetService inside the constructor:

    public MyView()
    {
        InitializeComponent();
                MyService myService = App.Services.GetService<MyService>();   // App.Services property defined during App construction.
                ...
    }
FcAYH commented 1 year ago

@Okanalagas1 I haven't follow MAUI for a while, so I dont know if MAUI support DI for custom controls now. In my project, I use this method:

public ItemBarcodeControl()
{
    InitializeComponent();

    IsVisibleButtonGrid = "true";
    IsVisibleDetailGrid = "false";

    var factory= ServiceProvider.GetService<IHttpClientFactory>();
    _client = factory?.CreateClient(AppConstants.ApiName);
}

And then you should register service in the MauiProgram.cs as follows:

 builder.Services.AddSingleton((IServiceProvider) =>
        {
            var factory = new IHttpClientFactory ();
            // Do something to init factory.
            return factory;
        });

Hope it can be helpful to you.

michaelrinderle commented 12 months ago

@FcAYH It looks like this ticket is still open without resolution, thanks for the workaround.

plppp2001 commented 11 months ago

I get the error for my ContentView control, and I'm also unable to do DI with it, if I have the following code, I get this error from the page: (everything is DI'ed)

using MauiKanApp.ViewModel.Login;

namespace MauiKanApp.Controls;

public partial class LoginControl : ContentView { public LoginControl(LoginControlViewModel viewModel) { InitializeComponent();

    //https://github.com/dotnet/maui/issues/4538
    this.BindingContext = viewModel;
}

}

within the page:

.....

FcAYH commented 11 months ago

I get the error for my ContentView control, and I'm also unable to do DI with it, if I have the following code, I get this error from the page: (everything is DI'ed)

using MauiKanApp.ViewModel.Login;

namespace MauiKanApp.Controls;

public partial class LoginControl : ContentView { public LoginControl(LoginControlViewModel viewModel) { InitializeComponent();

    //https://github.com/dotnet/maui/issues/4538
    this.BindingContext = viewModel;
}

}

within the page:

..... <Button BackgroundColor="Red"

..... etc

ERROR: Severity Code Description Project File Line Suppression State Error XLS0507 Type 'LoginControl' is not usable as an object element because it is not public or does not define a public parameterless constructor or a type converter............... etc

Yes, MAUI is still not support DI for custom controls.

plppp2001 commented 11 months ago

Yes, MAUI is still not support DI for custom controls.

I have learned something new today.

FcAYH commented 10 months ago

Oh? Is it, what's interesting? Can DI related issues be solved?

I have learned something new today. @plppp2001

plppp2001 commented 10 months ago

Oh? Is it, what's interesting? Can DI related issues be solved?

I have learned something new today. @plppp2001

I didn't know that with Maui controls, we can't DI them. πŸ˜Άβ€πŸŒ«οΈ

FcAYH commented 10 months ago

okok

from F_CIL : May you have a nice day.


From: Paul Astro @.> Sent: Thursday, January 25, 2024 1:01:56 AM To: dotnet/maui @.> Cc: F_CIL @.>; Mention @.> Subject: Re: [dotnet/maui] Dependency Injection for Custom Controls (Issue #4538)

Oh? Is it, what's interesting? Can DI related issues be solved?

I have learned something new today. @plppp2001https://github.com/plppp2001

I didn't know that with Maui controls, we can't DI them. πŸ˜Άβ€πŸŒ«οΈ

β€” Reply to this email directly, view it on GitHubhttps://github.com/dotnet/maui/issues/4538#issuecomment-1908554400, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AMYIJKHD7UXFSMDE76YN2KLYQE5AJAVCNFSM5NXCZ7NKU5DIOJSWCZC7NNSXTN2JONZXKZKDN5WW2ZLOOQ5TCOJQHA2TKNBUGAYA. You are receiving this because you were mentioned.Message ID: @.***>