Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MarkupExtension: IRootObjectProvider not available in release mode #16881

Open
loop8ack opened this issue Aug 20, 2023 · 18 comments
Open

MarkupExtension: IRootObjectProvider not available in release mode #16881

loop8ack opened this issue Aug 20, 2023 · 18 comments
Assignees
Labels
area-xaml XAML, CSS, Triggers, Behaviors s/triaged Issue has been reviewed s/verified Verified / Reproducible Issue ready for Engineering Triage t/bug Something isn't working
Milestone

Comments

@loop8ack
Copy link

loop8ack commented Aug 20, 2023

Description

The ServiceProvider passed to the ProvideValue method of a MarkupExtension returns an IRootObjectProvider in debug mode, but not in release mode.

I have been able to reproduce it with the following versions:

  • 7.0.92
  • 8.0.0-preview.7.8842

Steps to Reproduce

Create a new MAUI project and use the following code:

public class MyTestExtension : IMarkupExtension<string>
{
    public string ProvideValue(IServiceProvider serviceProvider)
        => (serviceProvider.GetService(typeof(IRootObjectProvider)) is not null).ToString();

    object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
        => ProvideValue(serviceProvider);
}
<Label Text="{local:MyTestExtension}" />

Output in debug mode: true
Output in release mode: false

All other services are available in both debug and release mode

Link to public reproduction project repository

Maui.Issue16881.Reproduction

Version with bug

8.0.0-preview.7.8842

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

Unknown/Other

Affected platforms

I was not able test on other platforms

Affected platform versions

No response

Did you find any workaround?

No response

Relevant log output

No response

@loop8ack loop8ack added the t/bug Something isn't working label Aug 20, 2023
@drasticactions drasticactions added the s/needs-repro Attach a solution or code which reproduces the issue label Aug 21, 2023
@ghost
Copy link

ghost commented Aug 21, 2023

Hi @loop8ack. 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.

@loop8ack
Copy link
Author

I have created the following repository for reproduction: Maui.Issue16881.Reproduction

Just run it and look at the output.
True: IRootObjectProvider was found.
False: IRootObjectProvider was not found.

@ghost ghost added s/needs-attention Issue has more information and needs another look and removed s/needs-repro Attach a solution or code which reproduces the issue labels Aug 21, 2023
@rmarinho rmarinho added area-xaml XAML, CSS, Triggers, Behaviors and removed s/needs-attention Issue has more information and needs another look labels Aug 25, 2023
@rmarinho rmarinho added this to the Backlog milestone Aug 25, 2023
@ghost
Copy link

ghost commented Aug 25, 2023

We've added this issue to our backlog, and we will work to address it as time and resources allow. If you have any additional information or questions about this issue, please leave a comment. For additional info about issue management, please read our Triage Process.

@rmarinho
Copy link
Member

What platform does this happens?

Any thoughts @StephaneDelcroix ?

@AnnYang01
Copy link

AnnYang01 commented Aug 28, 2023

Verified this on Visual Studio Enterprise 17.8.0 Preview 1.0 using below Project (.NET 8.0), This issue repro on Windows 11 and Android 13.0-API33.
Maui.Issue16881.Reproduction-main.zip
Windows 11
image
Android 13.0-API33
image

@AnnYang01 AnnYang01 added s/verified Verified / Reproducible Issue ready for Engineering Triage s/triaged Issue has been reviewed labels Aug 28, 2023
@SirJohnK
Copy link

SirJohnK commented Jul 19, 2024

What's the status on fixing this? I have this issue on version 8.0.100.
Any other way to get the page/view from a MarkupExtension?

@Wout-M
Copy link

Wout-M commented Jul 19, 2024

I have encountered the same issue when working on a similar project as @SirJohnK in Maui 8.0.40

@SirJohnK
Copy link

Seems like the XamlServiceProvider gets an null Context or an null Context.RootElement when in Release mode:

public class XamlServiceProvider : IServiceProvider
{
	readonly Dictionary<Type, object> services = new Dictionary<Type, object>();
		internal XamlServiceProvider(INode node, HydrationContext context)
	{
		if (context != null && node != null && node.Parent != null && context.Values.TryGetValue(node.Parent, out object targetObject))
			IProvideValueTarget = new XamlValueTargetProvider(targetObject, node, context, null);
		if (context != null)
			IRootObjectProvider = new XamlRootObjectProvider(context.RootElement);

Any thoughts on this @StephaneDelcroix ?

@SirJohnK
Copy link

SirJohnK commented Aug 9, 2024

Found a workaround until this is fixed. Root object can be retrieved from IProvideValueTarget with Reflection in Release mode!

Made this extension method:

public static object? GetRootObject(this IProvideValueTarget provideValueTarget)
{
    //Attemp to get the root object from the provided value target
    var pvtType = provideValueTarget?.GetType();
    var parentsInfo = pvtType?.GetProperty("Microsoft.Maui.Controls.Xaml.IProvideParentValues.ParentObjects", BindingFlags.NonPublic | BindingFlags.Instance);
    var parents = parentsInfo?.GetValue(provideValueTarget) as IEnumerable<object>;
    return parents?.LastOrDefault();
}

@kfrancis
Copy link

Seeing this now that we're trying to finally get our app out!

@kfrancis
Copy link

Here's where this all breaks down for us. IRootObjectProvider is null in release always:

/// <summary>
/// The base localization manager.
/// </summary>
[ContentProperty(nameof(Text))]
public class TranslateExtension : IMarkupExtension<BindingBase>
{
    public object? BindingContext { get; set; }
    public IValueConverter? Converter { get; set; }
    public object? ConverterParameter { get; set; }
    public string? StringFormat { get; set; }

    /// <summary>
    /// The text for localization.
    /// </summary>
    public string Text { get; set; } = string.Empty;

    public virtual string GetResourceString(IStringLocalizer<CabMdResource> loc)
    {
        ArgumentNullException.ThrowIfNull(loc);

        return loc[Text];
    }

    /// <summary>
    /// Gets the localization text.
    /// </summary>
    /// <param name="serviceProvider">The service provider.</param>
    public BindingBase ProvideValue(IServiceProvider serviceProvider)
    {
        // Try to get IRootObjectProvider; return the text itself if it's missing
        var rootObjectProvider = serviceProvider.GetService<IRootObjectProvider>();
        if (rootObjectProvider == null)
        {
            // If IRootObjectProvider is unavailable, fallback to returning a simple binding to the Text property
            return new Binding
            {
                Mode = BindingMode.OneWay,
                Path = Text
            };
        }

        if (rootObjectProvider.RootObject is not BindableObject bindableObject || bindableObject.BindingContext is not ViewModelBase viewModelBase)
        {
            return new Binding
            {
                Mode = BindingMode.OneWay,
                Path = Text
            };
        }

        var localizationResourceManager = viewModelBase.L;

        return new Binding
        {
            Mode = BindingMode.OneWay,
            Path = $"[{Text}]",
            Source = localizationResourceManager,
            StringFormat = StringFormat,
            Converter = Converter,
            ConverterParameter = ConverterParameter
        };
    }

    object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
    {
        return (this as IMarkupExtension<BindingBase>).ProvideValue(serviceProvider);
    }
}

I've tried that workaround @SirJohnK, but I'm not sure how it applies in this context. Also, moving to your lib looks like so much work for us .. coming from an abp.io/mvvm scenario.

@loop8ack
Copy link
Author

@kfrancis

I've tried that workaround @SirJohnK, but I'm not sure how it applies in this context.

IProvideValueTarget is a service that you can receive from the IServiceProvider.
With the extension method from @SirJohnK, it should look like this:

var rootObjectProvider = serviceProvider.GetService<IRootObjectProvider>();

var rootObject = rootObjectProvider == null
    ? serviceProvider.GetService<IProvideValueTarget>()?.GetRootObject()
    : rootObjectProvider.RootObject;

Heads up: I wrote this in the browser and have not tested it.

@SirJohnK
Copy link

@loop8ack, was about to say exactly that!👍

By the way, the latest prerelease version of my library is on NuGet and includes some really neat features, if I say so myself. 😄 (where this extension method is used for page specific resources 😅)

@johan-broekhof
Copy link

In .NET 9 the RequireService attribute was introduced. The IRootObjectProvider is still null even though it is set as required service... Furthermore, when I add IProvideValueTarget as required service, it is available but the solution provided by @SirJohnK does not work anymore: the root object is null...

Has anyone figured out how to get this to work in combination with the attribute?

@SirJohnK
Copy link

In .NET 9 the RequireService attribute was introduced. The IRootObjectProvider is still null even though it is set as required service... Furthermore, when I add IProvideValueTarget as required service, it is available but the solution provided by @SirJohnK does not work anymore: the root object is null...

Has anyone figured out how to get this to work in combination with the attribute?

@StephaneDelcroix , can you PLEASE comment on this!?

  • Why is it like this?
  • Will it be changed?
  • Why does Release build differ from Debug in this case?

@StephaneDelcroix
Copy link
Contributor

Xaml inflator differs from Release and Debug, that's why it behave differently. IXamlRootObjectProvider was never available with XamlC inflator (Release builds)

the purpose of IXamlRootObjectProvider is to help with built-in markup extension and was never meant to be used publicly. not sure why one would need it...

@StephaneDelcroix StephaneDelcroix self-assigned this Feb 13, 2025
@SirJohnK
Copy link

Xaml inflator differs from Release and Debug, that's why it behave differently. IXamlRootObjectProvider was never available with XamlC inflator (Release builds)

the purpose of IXamlRootObjectProvider is to help with built-in markup extension and was never meant to be used publicly. not sure why one would need it...

@StephaneDelcroix , thanks for commenting!

  • In my Maui Localization library, LocalizationResourceManager.Maui, this is key requirement for using specific Resources for all MarkupExtensions on a Page.

  • By getting the RootObject I can inspect that and check if a specific Resource is defined for that Page. (In my case, an interface called ISpecificResourceManager)

  • See https://github.com/SirJohnK/LocalizationResourceManager.Maui/blob/Specific_ResourceManager/LocalizationResourceManager.Maui/Extensions/TranslateExtension.cs

  • It is confusing with the RequireService attribute when IRootObjectProvider type is added and it still is NULL.

  • A dream scenario is that any type added to RequireService would be retrieved from the main Maui DI container.

  • Would it be possible to at least get the IRootObjectProvider type in a Release build?

Thank for your great work! 👍

/Regards Johan

@SirJohnK
Copy link

SirJohnK commented Feb 23, 2025

I found a .NET 9 solution!!! 😄

Add BOTH IReferenceProvider AND IProvideValueTarget as RequireService:

[RequireService([typeof(IReferenceProvider), typeof(IProvideValueTarget])]

With this, my IProvideValueTarget extension method GetRootObject works! (See my earlier post)

Really unfortunate that we can't just get IRootObjectProvider, since the information is clearly available even i Release mode!

  • The IRootObjectProvider interface is Public and indicates that it should be available.
  • Hopefully we will get it in future updates of .NET MAUI.

/Johan

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-xaml XAML, CSS, Triggers, Behaviors s/triaged Issue has been reviewed s/verified Verified / Reproducible Issue ready for Engineering Triage t/bug Something isn't working
Projects
None yet
Development

No branches or pull requests

9 participants