Open luis95gr opened 1 year ago
Try to change the order like this:
public partial class LoginPage : ContentPage
{
public LoginPage(LoginPageViewModel loginPageViewModel)
{
InitializeComponent();
BindingContext = loginPageViewModel;
}
}
Try to change the order like this:
public partial class LoginPage : ContentPage { public LoginPage(LoginPageViewModel loginPageViewModel) { InitializeComponent(); BindingContext = loginPageViewModel; } }
Thank you for the response, but the error still happens even with that change.
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.
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:
@luis95gr - we don't seem to have access to https://github.com/luis95gr/NewScholarApp. Can you ensure the repo is public?
Hi @luis95gr. We have added the "s/needs-repro" label to this issue, which indicates that we require steps and sample code to reproduce the issue before we can take further action. Please try to create a minimal sample project/solution or code samples which reproduce the issue, ideally as a GitHub repo that we can clone. See more details about creating repros here: https://github.com/dotnet/maui/blob/main/.github/repro.md
This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.
This issue has been automatically marked as stale because it has been marked as requiring author feedback to reproduce the issue but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment. If it is closed, feel free to comment when you are able to provide the additional information and we will re-investigate.
@luis95gr - we don't seem to have access to https://github.com/luis95gr/NewScholarApp. Can you ensure the repo is public?
I just update the original post with a public repo. This is the link: https://github.com/luis95gr/SampleProject
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
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.
Hi @symbiogenesis
Thanks for the input,
MauiApp1.zip I have attached my implementation in basic project. Please modify it with your implementation or share if you have any working sample project with IServiceProvider implementation in MAUI. It will help many .NET folks
Thanks in advance
Could anybody explain the likely cause of this issue in a nutshell? We have hit it too.
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.
@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;
}
With this code, resolving the resource works fine:
public App(IServiceProvider services)
{
InitializeComponent();
var appShell = services.GetRequiredService<AppShell>();
MainPage = appShell;
}
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.
Having the same problem on iOS
I hit this issue also. I fixed in this way:
public partial class App : Application
{
public App(IServiceProvider serviceProvider)
{
InitializeComponent();
MainPage = serviceProvider.GetRequiredService<MainPage>();
}
}
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):
MainPage = mainPage;
) - probably your tech writers can add a note there. CC @Eilon Thank you, Everyone.
Verified this issue with Visual Studio Enterprise 17.8.0 Preview 3.0. Can repro on android platform with sample project.
SampleProject-master.zip
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 issue was reported a whole year ago and noone from the team reacted so far. StaticResources are a fundamental thing for ANY real world app that is not just another Maui Sample. Seriously, you can't be ignoring this issue every single enterprise customer is dependent on.
Hi, I'm from the team 😁
Here's what I've understood from this thread:
Using .NET MAUI + XAML + StaticResources + DI for pages causes an exception. To avoid the exception, the code can be rewritten to take in the IServiceProvider
in the constructor (instead of taking in the page), and then the service provider can be asked to retrieve the page after InitializeComponent()
is called. This workaround has been posted earlier in this thread, and can be summarized like this:
Causes exception: (Microsoft.Maui.Controls.Xaml.XamlParseException: 'Position row:column. StaticResource not found for key MyStaticResource'
)
public App(LoginPage page)
{
InitializeComponent();
MainPage = page;
}
Works:
public App(IServiceProvider sp)
{
InitializeComponent();
MainPage = sp.GetService<LoginPage>();
}
The cause of this is that XAML creates object trees outside-in, meaning the outer-most container is created first (say, the app container), then the next deeper one (say, a page or layout), etc., until finally the inner-most content is created (say, a button). However, Microsoft.Extensions.DependencyInjection creates object trees inside-out, meaning the inner-most item is created first, then whatever contains it is created next, until eventually the thing you 'asked' for is finally created.
This conflict is problematic because of the two different patterns: outside-in vs. inside-out.
The reason the workaround works is that it waits to do the DI part until after the XAML part is sufficiently done.
Given this fairly fundamental conflict, I'd like to hear on suggestions for how we can make this better.
If anyone has some additional thoughts on how we can improve this, please let us know.
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.
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 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.
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 have a complex app with more than 30 pages and a bunch of services and the only think I´ve did was on the App.xaml.cs at the constructor apply @Eilon workarround, then I´ve added all my static resources at App.xaml at:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
Then on each page I use propertly the DI. Here by an example:
MauiProgram.cs:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});
# register services
builder.Services.AddSingleton<IConnectivity>((e) => Connectivity.Current);
# register pages
builder.Services.AddSingleton<MainPage>();
# register view models
builder.Services.AddSingleton<MainPageViewModel>();
return builder.Build();
}
}
App.xaml
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:MauiApp1"
x:Class="MauiApp1.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
<!-- Add here your Resource Dictionaries -->
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
App.xaml.cs
public partial class App : Application
{
public App(IServiceProvider sp)
{
InitializeComponent();
MainPage = MainPage = sp.GetService<MainPage>();
}
}
MainPage.xaml.cs
public partial class MainPage : ContentPage
{
int count = 0;
public MainPage(MainPageViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
}
...
MainPageViewModel.cs
public class MainPageViewModel
{
private readonly IConnectivity connectivityService
public MainPageViewModel(IConnectivity connectivity)
{
connectivityService = connectivity ?? throw new ArgumentNullException(nameof(connectivity));
}
}
can provide a simplified repro that shows the issue you're having I can try to investigate
Hard to provide as this is a larger app I am currently "upgrading" (not sure about that) from Xamarin.Forms to Maui. Because all the efforts with the upgrade-assistant went directly to hell, I started a new Maui app and manually moved class for class from my older project to the newer one. So it's not that I threw in loads of old tech, manifests and plists. Still, I just cannot get this thing to work, but without knowing why, it's hard to build a repro.
My code however looks very much like the one @Rabosa616 just posted. However, the troublemakers in my case are static resources in the XAML itself:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
x:Name="anyPage"
x:Class="MyApp.Pages.AnyPage"
Title="{StaticResource AppResources.MyAppName}">
<ContentPage.Content>
...
I just found this closed issue #3878 which is about the same thing, there's a repro repository.
Okay wait, I am very confused to report that this works:
Title="{Static AppResources.MyAppName}">
While this does not:
Title="{StaticResource AppResources.MyAppName}">
Exception has occurred: Microsoft.Maui.Controls.Xaml.XamlParseException
Position 15:2. StaticResource not found for key AppResources.MyAppName
at at Microsoft.Maui.Controls.Xaml.ApplyPropertiesVisitor.ProvideValue(Object& value, ElementNode node, Object source, XmlName propertyName)
at at Microsoft.Maui.Controls.Xaml.ApplyPropertiesVisitor.Visit(ElementNode node, INode parentNode)
at at Microsoft.Maui.Controls.Xaml.ElementNode.Accept(IXamlNodeVisitor visitor, INode parentNode)
at at Microsoft.Maui.Controls.Xaml.RootNode.Accept(IXamlNodeVisitor visitor, INode parentNode)
at at Microsoft.Maui.Controls.Xaml.XamlLoader.Visit(RootNode rootnode, HydrationContext visitorContext, Boolean useDesignProperties)
at at Microsoft.Maui.Controls.Xaml.XamlLoader.Load(Object view, String xaml, Assembly rootAssembly, Boolean useDesignProperties)
at at Microsoft.Maui.Controls.Xaml.XamlLoader.Load(Object view, String xaml, Boolean useDesignProperties)
at at Microsoft.Maui.Controls.Xaml.XamlLoader.Load(Object view, Type callingType)
at at Microsoft.Maui.Controls.Xaml.Extensions.LoadFromXaml[LandingPage](LandingPage view, Type callingType)
at MyApp.Pages.AnyPage.InitializeComponent() in
I am not sure if this is the way it should be when working when using resx files for localization, I used StaticResource
with Xamarin.Forms over the last years.
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.
Maybe this helps!
I was faced with this issue the last week and struggled to find any solution that worked. None of the above possible solutions worked for me. Then eventually I found that I had added a Style property on some of my controls that was invalid. The style was in the styles.xaml file, but I was using it incorrectly. And the error was clear enough that the style was not found. Once I removed those Style properties, the app worked using the DI, etc. The only bug though is the fact that the app would run when attached to the debugger, but it would crash when not. The way I managed to solve it better was to run the Windows app version, which does in fact give the error when attached to the debugger. Hope this helps someone, and also helps in resolving the actual problem.
I was faced with this issue the last week and struggled to find any solution that worked. None of the above possible solutions worked for me. Then eventually I found that I had added a Style property on some of my controls that was invalid. The style was in the styles.xaml file, but I was using it incorrectly. And the error was clear enough that the style was not found. Once I removed those Style properties, the app worked using the DI, etc. The only bug though is the fact that the app would run when attached to the debugger, but it would crash when not. The way I managed to solve it better was to run the Windows app version, which does in fact give the error when attached to the debugger. Hope this helps someone, and also helps in resolving the actual problem.
the cause debug × release might be interpreted vs compiled XAML what I read in this repository sometimes.
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.
@Eilon - my feedback:
I agree with this recommendation by @symbiogenesis to add the IServiceProvider parameter to the App constructor in the default MAUI project template. This workaround is simple, DI-centric, and the issue should only appear when injecting the initial MainPage into the App constructor. No need to make this a big deal.
I note that @jfversluis 's DI injection sample project injects a MainPage into the App constructor, and it runs without this issue.... even in my pull request that upgrades it to .NET8.
btw proper DI (everything in the app resolved from DI) allows developers to validate DI setup on application start. If anything is resolved later during runtime using service locator antipattern, that cannot be done.
The code for doing that is (console and desktop)
private static void ValidateServiceDependenciesOnStartup(IHostBuilder builder)
{
builder.UseDefaultServiceProvider(options =>
{
options.ValidateOnBuild = true; //validate on builder.Build()
});
}
For example in console or desktop apps, the following code can be used to achieve that being called:
IHostBuilder builder = Host.CreateDefaultBuilder();
For ASP.NET Core (builder is WebApplicationBuilder
present in the default application template code):
builder.Host.UseDefaultServiceProvider((context, options) =>
{
options.ValidateOnBuild = true; //validate on builder.Build()
});
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
here hostBuilder
is accessed from the property WebApplicationBuilder.Host
which is of type ConfigureHostBuilder : IHostBuilder
This works in ASP.NET Core apps, console apps and desktop apps.
This is not possible in MAUI afaik
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):
LoginPage.xaml.cs
LoginPageViewModel
LoginPage.xaml
RegisterViews and ViewModels
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