2

My Goal is to NOT create dependencies between ViewModels.
I have .NET 7 MAUI project.
The situation is simple.

I have the MainPage and I linked ViewModel MainPageViewModel to it using DI

services.AddSingleton<MainPageViewModel>();

services.AddSingleton<MainPage>(provider =>
{
    return new MainPage()
    {
        BindingContext = provider.GetRequiredService<MainPageViewModel>(),
    };
});

It is working fine, the ViewModel is created and it is assigned to as BindingContext of the MainPage, Great.

Now, When I put another Child View inside the MainPage like this

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MyNamespace"
             x:Class="MyNamespace.MainPage">
    
            <MyNamespace:ChildView />

</ContentPage>

and I register it with its ViewModel like this

services.AddSingleton<ChildViewModel>();

services.AddSingleton<ChildView>(provider =>
{
    return new ChildView()
    {
        BindingContext = provider.GetRequiredService<ChildViewModel>(),
    };
});

The MainPage create the ChildView directly (by calling its parameterless constructor) without consulting the DI Container
This is causing the lack of Binding for the ViewModel.

  1. I do not want to Create ChildViewModel inside MainPageViewModel and pass it.
  2. In Prism this problem could be solved using Regions, but for now Prism does not support MAUI
  3. The creating of the MainPage is like this
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
    x:Class="Trex.Mobile.App.AppShell"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:local="clr-namespace:MyNamespace"
    Shell.FlyoutBehavior="Disabled">

    <ShellContent
        Title="Home"
        ContentTemplate="{DataTemplate local:MainPage}"
        Route="MainPage" />

</Shell>

and by using this way, the DI container is used.
Can I use something like that for my ChildView or should I change something fundamentally

3
  • Does this answer your question? Dependency Injection when nesting Views in MAUI. [OPINION] Custom components (ChildView) often want access to their parent view's BindingContext. This means NOT setting BindingContext = ChildViewModel. instead, place those properties directly in ChildView. See Fix #3 in stackoverflow.com/a/76493901/199364. Commented Jul 7, 2023 at 18:46
  • Yes, basically, it is the same idea as the answer below is mentioning Commented Jul 7, 2023 at 18:51
  • Good; thanks for confirming that this question can be closed as a duplicate. (I like the answer below, so I will link to it on the other question. Reach all good answer from there.) Commented Jul 7, 2023 at 18:58

1 Answer 1

4

I don't think it's possible to create the ChildView using the DI container, because Shell doesn't instantiate it.

Since the MainPage instance is created by Shell, you can use constructor injection:

public partial class MainPage : ContentPage
{
    public MainPage(MainPageViewModel viewModel)
    {
        InitializeComponent();
        BindingContext = viewModel;
    }
}

Then, as I have described in this answer, you can create a ServiceHelper class like so:

public static class ServiceHelper
{
    public static IServiceProvider Services { get; private set; }

    public static void Initialize(IServiceProvider serviceProvider) => 
        Services = serviceProvider;

    public static T GetService<T>() => Services.GetService<T>();
}

The ChildView is instantiated by the MainPage and cannot use Shell's constructor injection, but you can use the ServiceHelper from above to resolve the ChildViewModel:

public partial class ChildView : ContentView
{
    public ChildView()
    {
        InitializeComponent();
        BindingContext = ServiceHelper.GetService<ChildViewModel>();
    }
}

Finally, register all dependencies and setup the ServiceHelper:

builder.Services.AddSingleton<MainPage>();
builder.Services.AddSingleton<MainPageViewModel>();
builder.Services.AddSingleton<ChildView>();
builder.Services.AddSingleton<ChildViewModel>();

var app = builder.Build();

//we must initialize our service helper before using it
ServiceHelper.Initialize(app.Services);

return app;
Sign up to request clarification or add additional context in comments.

8 Comments

When I started the project, I created and used this ServiceHelper idea exactly as you described above, then I get rid of it because, as I remember, it was anti-pattern, and It never occurred to me again. I am really afraid that there would be no better solution. I think your solution will work, but I would like to see a better one. Thank you anyway, really appreciated.
here is an article that says it is anti-pattern, here is a question which say it is not, so I am really confused
Well, we need to consider the practicality of the approach. IoC containers usually rely on or rather enforce the Service Locator pattern and sometimes constructor injection is preferred. Now, with XAML we face the issue that only parameterless constructors can be used to instantiate ContentViews in XF and MAUI, so we don't have constructor injection available at all. Therefore, I do not regard the Service Locator pattern as an anti-pattern per sé when it is often a necessity.
[OPINION] The anti-pattern described in that article is already inherent in DI; adding a ServiceHelper does not make the problem worse. Specifically, DI breaks at run-time if referenced classes (services) are not registered with the DI container. Before DI, the written code, if it compiled, would work. Breaks that don't happen until run-time are bad for both usage and code maintenance. That's the article's point. DI is a trade-off: loose coupling at the cost of moving a failure from compile-time to run-time. Hopefully static analyzers will get smart enough to find at compile time.
I am slowly reaching the conclusion that this is the best solution available, I started implementing it and it worked, and I think I will go with this way, Thank you all
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.