maui: Can't use App.xaml resources after Dependency Injection .NET MAUI

Description

I have an application that uses mvvm pattern with Community Toolkit features. I am trying to use Dependency Injection to use an ApiService with its interface in my viewmodels, but after following the steps described here, I can’t access to App.xaml Resources (specifically colors), the intellisense works when I am writing code in XAML, but it doesn´t work after running. It is important to notice that I was using colors from resources correctly before trying to use Dependency Injection and changing the ViewModel - View linking method to the one described here, but I was unable to use ApiService. Here is my code.

Steps to Reproduce

App.xaml.cs (Login page is my first page):

public App(LoginPage page)
{
    InitializeComponent();
    MainPage = page;
}

LoginPage.xaml.cs

public partial class LoginPage : ContentPage
{
  public LoginPage(LoginPageViewModel loginPageViewModel)
  {
    BindingContext = loginPageViewModel;
    InitializeComponent();
  }
}

LoginPageViewModel

public LoginPageViewModel(IApiService apiService)
    {
        _apiService = apiService;
        Opacity = 1f;
        IsEnabled = true;
        IsRunning = false;
    }

LoginPage.xaml

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
         x:Class="NewScholarApp.Views.LoginPage"
         Title="LoginPage"
         xmlns:vm="clr-namespace:NewScholarApp.ViewModels"
         x:DataType="vm:LoginPageViewModel"
         BackgroundColor="{StaticResource GreenSchool}">

RegisterViews and ViewModels

public static MauiAppBuilder ConfigureViews(this MauiAppBuilder mauiAppBuilder)
    {
        mauiAppBuilder.Services.AddTransient<LoginPage>();

        return mauiAppBuilder;
    }

public static class RegisterViewModels
{

    public static MauiAppBuilder ConfigureViewModels(this MauiAppBuilder mauiAppBuilder)

    {
        mauiAppBuilder.Services.AddSingleton<LoginPageViewModel>();

        return mauiAppBuilder;
    }
}

Link to public reproduction project repository

https://github.com/luis95gr/SampleProject

Version with bug

7.0 (current)

Last version that worked well

Unknown/Other

Affected platforms

Android, I was not able test on other platforms

Affected platform versions

Android 12.1

Did you find any workaround?

Don’t use any StaticResource

Relevant log output

Microsoft.Maui.Controls.Xaml.XamlParseException: 'Position 8:14. StaticResource not found for key GreenSchool'

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Reactions: 8
  • Comments: 31 (7 by maintainers)

Most upvoted comments

I encountered this same issue when upgrading to MAUI and switching to the new DI style. This is a bug.

The options for workarounds I found are the following:

  1. Best: Inject IServiceProvider and use that to resolve the page only at the moment it is needed.
  2. Worse: For the App’s MainPage, inject only the ViewModel, not the page, and new-up the page. (Lose DI for the page)
  3. Even Worse: Inject the main page using System.Lazy as described here or here
  4. Worst of all: Convert all StaticResources in App.xaml to DynamicResources and incur the cost of doing that.

What is happening is that if you use DI to register ContentPage elements and maybe other UI elements, you will not be able to access static resources declared in your App.xaml file. I’m sure the technical reasons involve the app lifecycle assumptions deep within MAUI. A UI element resolved via constructor injection is initialized very early in the app or page lifecycle, and it can only access those resources later on. I believe I saw other bugs also logged about this, and maybe it will get fixed.

But essentially you can get around this by injecting IServiceProvider into the App class, and then using that to resolve the MainPage inside the App class, thereby forcing the resolution of the page element to occur later than the constructor injection would’ve resolved it. I demonstrated this approach in the comment above yours.

Another benefit of that approach is if you were injecting multiple modals/elements/popups into the same class, you would be able to ensure that those modals/elements/popups were only instantiated if needed, and only when they are needed. So you could paint the screen quicker, use less memory, etc.

I experienced the same behavior (“StaticResource not found for key ShellMenuItemTemplate”) when injecting a custom service into my MainPage (derived from Shell) as part of my pet project powered by DevExpress .NET MAUI project templates and controls. Your solution certainly worked (MainPage = serviceProvider.GetRequiredService<MainPage>();) and it is fine with me - thank you for documenting it here for others.

Please just consider the following documentation enhancements for the .NET MAUI developer community (not urgent):

  1. This DI specificity with StaticResource is not mentioned anywhere in the .NET MAUI docs ( https://learn.microsoft.com/en-us/dotnet/architecture/maui/dependency-injection), where the problematic approach in the App.xaml.cs is shown everywhere (MainPage = mainPage;) - probably your tech writers can add a note there. CC @Eilon
  2. I also feel that just like myself, many people searched the internet for prebuilt code samples using .NET MAUI + DI, “maui dependency injection”, and other relevant keywords). As a result, the following highly trustworthy resources from the recognized influencers demonstrated the same approach without highlighting this potential specificity - I am mentioning you directly just in case you may want to consider updates in the future. CC @jamesmontemagno (https://montemagno.com/tag/dependency-injection/ and https://youtu.be/ugem4UbAtC0) @jfversluis (https://youtu.be/wkgbvMlrMhU)

Thank you, Everyone.

I hit this issue also. I fixed in this way:

public partial class App : Application
    {
        public App(IServiceProvider serviceProvider)
        {
            InitializeComponent();

            MainPage = serviceProvider.GetRequiredService<MainPage>();
        }
    }

Hi @luis95gr I have a workaround for it, you can create service helper class with this code

public static class ServiceHelper
{
    public static TService GetService<TService>()
        => Current.GetService<TService>();

    public static IServiceProvider Current =>
#if WINDOWS10_0_17763_0_OR_GREATER
			MauiWinUIApplication.Current.Services;
#elif ANDROID
            MauiApplication.Current.Services;
#elif IOS || MACCATALYST
			MauiUIApplicationDelegate.Current.Services;
#else
			null;
#endif
}

and you can use it where ever it is required(lazy loading)

        MyViewModel myViewModel = ServiceHelper.Current.GetService<MyViewModel ();

Do not register pages in builder.Services, just register services and Viewmodels

Just for reference, for styles and colors, which are defined in a ResourceDictionary, I use {StaticResource ...}, for classes and static resources from code behind, I’m using {x:Static ...}. Haven’t had any problems yet, even with DI.

 <Label 
        Margin="2,16"
        Text="This is a headline"
        VerticalTextAlignment="Center" 
        HorizontalTextAlignment="Center"
        Style="{StaticResource PrimaryHeadlineLabelStyle}"
        />

    <Label 
        Margin="2,16"
        Text="{x:Static icons:MaterialIcons.HeartOutline}"
        VerticalTextAlignment="Center" 
        HorizontalTextAlignment="Center"
        Style="{StaticResource PrimaryMaterialFontFamilyIconLabelStyle}"
        />

The MaterialFontIcons class looks like that.

https://github.com/AndreasReitberger/SharedMauiXamlStyles/blob/main/src/SharedMauiXamlStylesLibrary/Models/FontIcons/MaterialIcons.cs

Maybe this helps!

I could not get the workaround to work for me. Therefore, I have no choice but to remove all static resources in order to continue my migration efforts. Might be another issue in my migrated codebase that is causing this in combination but as for almost every error I encounter, the error messages and callstacks are completely useless. This migration from a medium sized enterprise Xamarin.Forms app was the worst experience I’ve ever had as a developer and that truly means a lot. I used to be a Xamarin and especially Xamarin.Forms advocate. I have been in from the very beginning and gladly paying $999 yearly for Xamarin.iOS. These days I’m downright frustrated with how tedious the porting and how immature the runtime is. It’s a disastrous state and the way I see it, nothing has really changed with .NET 8.

Hi @awaescher if you can provide a simplified repro that shows the issue you’re having I can try to investigate. My familiarity is mostly with the DI side of things and much less so on the XAML/resources side. I wonder in your case are there multiple levels of XAML pages using static resources? If so, the workaround I mentioned in my comment would need to be applied at each level. Or, it could be something else entirely.

There could also be other completely different approaches, such as querying each service “on demand” once the page loads, instead of using constructor injection at all. While this would most likely work, because it allows all the XAML stuff to work “normally,” in some apps it could require significant changes to allow some functionality to happen after the page loads.

So we should use service locator antipattern because MAUI team cannot implement DI properly? No thanks

I could not get the workaround to work for me. Therefore, I have no choice but to remove all static resources in order to continue my migration efforts. Might be another issue in my migrated codebase that is causing this in combination but as for almost every error I encounter, the error messages and callstacks are completely useless.

This migration from a medium sized enterprise Xamarin.Forms app was the worst experience I’ve ever had as a developer and that truly means a lot. I used to be a Xamarin and especially Xamarin.Forms advocate. I have been in from the very beginning and gladly paying $999 yearly for Xamarin.iOS.

These days I’m downright frustrated with how tedious the porting and how immature the runtime is. It’s a disastrous state and the way I see it, nothing has really changed with .NET 8.

I think using a fully MVVM and DI-centric approach in the MAUI project templates would ensure people don’t run into this.

IServiceProvider would be used by default to grab the initial MainPage and the ViewModel for the AppShell.

We cannot inject the “MainPage” into App ctor if MainPage uses static resources from App.xaml (or further references xamls, e.g. Styles.xaml or Colors.xaml). The instance of MainPage is created by DI just before it is injected into App.xaml.cs’ constructor. We need to run App.xaml.cs’ InitializeComponent() method before we can run MainPage’s InitializeComponent() method.

public partial class App : Application
{
    public App(IServiceProvider serviceProvider)
    {
        this.InitializeComponent();

        var mainPage = serviceProvider.GetRequiredService<MainPage>();
        this.MainPage = new NavigationPage(mainPage);
    }
}

I would expect .NET MAUI to provide better support for Page/ViewModel handling with DI.

This StackOverflow question is another example of the problem.

As I explain in first comment there, the problem is a direct consequence of how DI works.

The assumption in XamarinForms/Maui has always been that App gets initialized, BEFORE any page is constructed. Using DI to inject a page into App constructor breaks that design assumption.

@symbiogenesis
Thanks a lot for pointing the root cause out. I did not know that actually the instance is lazily created when you call GetService()/GetRequiredService(), I assumed it is already created anyways and is just picked from the pile of the services living in the IServiceProvider. Injecting IServiceProvider really helped solving the issue. My use case was:

Injecting AppShell into App because AppShell needs injected AppShellViewModel to be able to control menu items visibility via bound properties.

Trying to achieve:

<Shell.FlyoutHeader>
        <VerticalStackLayout Padding="20, 14">
            <Label Text="User name:" TextColor="{StaticResource Primary}" FontAttributes="Bold"/>
            <Label Text="{Binding UserName}"/>
        </VerticalStackLayout>
    </Shell.FlyoutHeader>

With this code, resolving the resource failed with the error shown below:

public AppShell(AppShellViewModel viewModel)
    {
        InitializeComponent();
        this.BindingContext = viewModel;
    }

image

With this code, resolving the resource works fine:

public App(IServiceProvider services)
    {
        InitializeComponent();
        var appShell = services.GetRequiredService<AppShell>();
        MainPage = appShell;
    }

If you use DI for everything, you can just use constructor injection to resolve IServiceProvider from anywhere in your application, and you shouldn’t need a static IServiceProvider exposed.

Except maybe for integration testing purposes, and then you can use an ifdef for that case.

We’ve moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

Try to change the order like this:

public partial class LoginPage : ContentPage
{
  public LoginPage(LoginPageViewModel loginPageViewModel)
  { 
    InitializeComponent();
    BindingContext = loginPageViewModel;
  }
}