15

I'm trying to understand how to implement dependecy injection in a .NET MAUI app.

I have a service class -- and its interface -- that handle my REST calls that looks like this:

public class MyRestApiService : IMyRestApiService
{
   public async Task<string> Get()
   {
      // Do someting
   }
}

I then place this in my DI container in MauiProgram.cs:

builder.Service.AddTransient<IMyRestApiService, MyRestApiService>();

I also have a view model that I will use for my MainPage.xaml. The question is, if I do a constructor injection of my service, the XAML doesn't seem to like it.

The MainPageViewModel looks like this:

public class MainPageViewModel : BaseViewModel
{
   IMyRestApiService _apiService;
   public MainPageViewModel(IMyRestApiService apiService)
   {
      _apiService = apiService;
   }
}

When I tried to define MainPageViewModel as the view model for MainPage.xaml as below, I get an error:

<ContentPage.BindingContext>
   <vm:MainPageViewModel />
</ContentPage.BindingContext>

The error reads:

Type MainPageViewModel is not usable as an object element because it is not public or does not define a public parameterless constructor or a type converter.

How do I inject my services into my view models?

Sam
  • 26,817
  • 58
  • 206
  • 383

2 Answers2

12

You will want to resolve everything basically from the first page for everything to fall in place and for dependency injection to work.

Have a look at this example: https://github.com/jfversluis/MauiDependencyInjectionSample

You will want to register your services, view models and views. In your case, in your MauiProgram.cs add:

// Change scopes as needed, this seems to make sense
builder.Service.AddTransient<MainPage>();
builder.Service.AddTransient<MainPageViewModel>();
builder.Service.AddSingleton<IMyRestApiService, MyRestApiService>();

Then in your App.xaml.cs also start injecting your (main) page:

public partial class App : Application
{
    public App(MainPage page)
    {
        InitializeComponent();

        MainPage = page;
    }
}

Now in your MainPage.xaml.cs add a constructor like this:

public LoginPage(MainPageViewModel viewModel)
{
    InitializeComponent();

    BindingContext = viewModel;
}

From there everything should follow suit and be connected. What you are trying to do with

<ContentPage.BindingContext>
   <vm:MainPageViewModel />
</ContentPage.BindingContext>

Is basically setting the BindingContext through the property. You can, but then you'll have to specify the parameters yourself and resolve them from the dependency injection container somehow which typically you don't want to do.

Gerald Versluis
  • 30,492
  • 6
  • 73
  • 100
  • 1
    I'm doing this, too. One downside of this approach is that you'll loose XAML intellisense. To get it back, I'm setting the the design time `d:ContentPage.BindingContext` property to `vm:MainPageViewModel` in XAML. It complains that the view model can't be created (because it has no parameterless constructor) but it makes intellisence work again. – Michal Diviš May 30 '22 at 12:20
  • Ah great addition, thank you! Make sure there is an issue for that on the repo so we can make it better :) – Gerald Versluis May 30 '22 at 13:05
  • I've created a [PR](https://github.com/jfversluis/MauiDependencyInjectionSample/pull/1) with that enhancement. – Michal Diviš May 30 '22 at 15:17
  • Thank you. My main question was about the IntelliSense issue @MichalDiviš addressed but I do appreciate the detailed explanation. You actually use the same approach Michal mentioned in your Crash Course video #4 at 29:03. Also, one question: shouldn't the views and view models be `AddSingleton()` and `AddSingleton()`? – Sam May 30 '22 at 16:11
  • 2
    @MichalDiviš thanks for that! I did mean on the .NET MAUI repo though :D And Sam, you probably do not want to have singletons for your page and view models as they will now retain state. If you open a page for a certain product for instance and the user edits some things it might be that those edits end up being there for a different product that you open the next time. – Gerald Versluis May 30 '22 at 18:23
  • Hey @GeraldVersluis, sorry for that. Did you mean the maui-samples repo or a different one? – Michal Diviš May 31 '22 at 06:12
  • 2
    To get Intellisense back in the xaml part you can also add: ```xmlns:vm="clr-namespace:SomeViewModels"``` ```x:DataType ="vm:SomeViewModel"``` – Stefan Ott Oct 13 '22 at 07:50
3

To inject your view model into your view you actually need to do it in its constructor, in code behind, like this:

public partial class LoginPage : ContentPage {
    public LoginPage(ILoginViewModel loginViewModel) {
        BindingContext = loginViewModel;
        InitializeComponent();
    }
}

Also you have to register views that use dependency injection:

builder.Service.AddTransient<LoginPage>();

Afaik you can't instantiate a view model with DI in XAML like you are doing

LoRdPMN
  • 512
  • 1
  • 5
  • 18