Search code examples
c#.netmaui.net-maui.shell

Create Child View using .NET DI container .NET MAUI


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


Solution

  • 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;