1

I am attempting to nest Views, which use MVVM and hence have a ViewModel injected into them. The code behind for the OuterView as well as the xaml for the InnerView are not really relevant, so I am leaving them, as well as all functionality, out for brevity. The Views and ViewModels are registered as Services, so DI should work.
The structure is as follows:

public partial class {
   public InnerView(InnerViewModel viewModel){
       BindingContext = viewModel;
       InitializeComponent();
   }
}
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:pages="clr-namespace:Example.Views"
             x:Class="Example.Views.OuterView"
             x:DataType="viewModels:OuterViewModel">
    <VerticalStackLayout name="InnerViewWrapper">
        <pages:InnerView/>
    </VerticalStackLayout>
</ContentPage>

This gives me the error "Type 'InnerView' is not usable as an object element because it [...] does not not define a parameterless constructor [...]". This makes sense, so I started just adding the View in my code-behind.

StockHistoryGraph? view = Handler?.MauiContext?.Services.GetServices<StockHistoryGraph>().FirstOrDefault(defaultValue: default);
if (view is not null) 
    InnerViewWrapper.Add(view);

This however seems quite tedious, so my question is:
Is there a way to use DI on a View which is declared in a xaml file?

  • 1
    How did you add ```InnerViewModel``` into the DI? – tval Jun 16 '23 at 12:42
  • usually when you have a nested view, you want it to inherit it's `BindingContext` from the parent, not use a fixed context – Jason Jun 16 '23 at 14:17
  • Also personally I am not a fan of creating Views through DI they just make things messy and your builder heavier for no apparent reason – FreakyAli Jun 16 '23 at 18:13
  • [ServiceHelper class is another useful approach](https://stackoverflow.com/a/76635084/199364). (On a question that is marked as a duplicate of this one.) – ToolmakerSteve Jul 07 '23 at 19:09

1 Answers1

1

Here are three ways to fix InnerView so that it works with a parameterless constructor.
First two involve setting BindingContext in code-behind.
Third avoids needing BindingContext.
In all solutions, InnerView MUST NOT have XAML that sets BindingContext.


FIX #1: new InnerViewModel instead of DI:

public InnerView()
{
    InitializeComponent();
    BindingContext = new InnerViewModel();
}

FIX #2: Explicitly resolve the service (works even if InnerViewModel needs DI-injected parameters):

public InnerView()
{
    InitializeComponent();
    BindingContext = Application.Current.Handler.MauiContext.Services.GetService<InnerViewModel>();
}

FIX #3: Code InnerView so it accesses its properties WITHOUT setting BindingContext:

  <SomeElement SomeAttribute="{Binding MyProperty, Source={RelativeSource Self}}" />
  <-- OPTIONAL -->
  <SomeElement SomeAttribute="{Binding VM.MyProperty2, Source={RelativeSource Self}}" />
public partial class InnerView : ContentView
{
    // OPTIONAL: If you really want a view model for this custom component
    // (usually not needed; just put properties in "this",
    //  as seen with "MyProperty".)
  private InnerViewModel VM { get; set; }

  public SomeType MyProperty { get; set; }

  public InnerView()
  {
    InitializeComponent();
    // NO "BindingContext = ...;"

    // OPTIONAL
    VM = new InnerViewModel();
    // OR (works even if InnerViewModel has DI-injected parameters)
    VM = Application.Current.Handler.MauiContext.Services.GetService<InnerViewModel>();
  }
}

// OPTIONAL
public class InnerViewModel
{
    public SomeType2 MyProperty2 { get; set; }
}

BONUS: App.Services property

To shorten:

Application.Current.Handler.MauiContext.Services

to:

App.Services

Ex: ... = App.Services.GetService<SomeType>();.

Inject service provider into App's (App.xaml.cs) constructor, and set a property to it:

... class App
{
    public readonly IServiceProvider Services;

    public App(IServiceProvider services)
    {
        ...

        Services = services;
    }
}

ServiceHelper class is an alternative to App.Services.

ToolmakerSteve
  • 18,547
  • 14
  • 94
  • 196