Bug 57139 - XAMLC: Binding created through Markup Extension doesn't work
Summary: XAMLC: Binding created through Markup Extension doesn't work
Status: RESOLVED ANSWERED
Alias: None
Product: Forms
Classification: Xamarin
Component: Forms ()
Version: 2.3.4
Hardware: PC Windows
: --- normal
Target Milestone: ---
Assignee: Stephane Delcroix
URL:
Depends on:
Blocks:
 
Reported: 2017-06-05 12:28 UTC by Philipp Sumi
Modified: 2017-06-06 12:25 UTC (History)
2 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:

Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report on Developer Community or GitHub with your current version information, steps to reproduce, and relevant error messages or log files if you are hitting an issue that looks similar to this resolved bug and you do not yet see a matching new report.

Related Links:
Status:
RESOLVED ANSWERED

Description Philipp Sumi 2017-06-05 12:28:12 UTC
I'm using a simple MarkupExtension in all my XAML to display localized strings, optionally with a binding like so:

    <Label Text="{localization:Res SomeResourceKey, Path=SomeViewModelProperty}" />

The difference between specifying a path or not is that the extension returns a Binding with a StringFormat in case of a path, or just the resource value if there is no path.


Now, this worked just fine until I switched on XAMLC - no I'm ending up with an empty label *IF* I specify the path in order to format a string. This on the other hand resolves just fine:

    <Label Text="{localization:Res SomeResourceKey}" />


I tried hardcoding the binding with StringFormat, and that works too. Also, when setting a breakpoint in the markup extension, I see that the binding is properly constructed and returned in any case. So it appears that XamlC does something that causes the binding to malfunction if delivered through the MarkupExtension.

Extension is this here:


    [ContentProperty("ResourceKey")]
    public class Res : IMarkupExtension
    {
        public string Path { get; set; }

        public string ResourceKey { get; set; }

        
        object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
        {
            try
            {
                string resourceValue = AppResources.ResourceManager.GetString(ResourceKey);

                if (String.IsNullOrEmpty(Path))
                {
                    return resourceValue;
                }
                else
                {
                    return new Binding(Path) { StringFormat = resourceValue };
                }
            }
            catch
            {
                return ResourceKey;
            }
        }
    }
Comment 1 Philipp Sumi 2017-06-05 13:04:30 UTC
Oh, discovered this one in the debugger output. That will hopefully make it easier to pinpoint the issue:

[0:] SetValue: Can not convert Xamarin.Forms.Binding to type 'System.String'
Comment 2 Stephane Delcroix 2017-06-05 13:07:54 UTC
This is the expected behavior. if your markup extnension returns a Binding, implements IMarkupExtension<BindingBase> instead of IMarkupExtension. Your intention will then be clearly declared to the xaml parser and compiler.

note that it matters that you specify BindingBase instead of Binding (as SetBinding accepts a BindingBase)
Comment 3 Philipp Sumi 2017-06-05 13:17:30 UTC
Stephane, thanks for the quick feedback!

I have two questions:

- runtime compilation handled this just fine so far. Any idea why, and if that is buggy behavior?
-  In most cases (when I have no parameter), I don't return a binding but just the string. Are you saying I should use two markup extensions for these two cases, with the later extending IMarkupExtension<BindingBase>?
Comment 4 Stephane Delcroix 2017-06-06 11:04:09 UTC
Runtime (with XamlC off) has the real object in hand (a Binding), and can figure out what to do when you try to set it to a BindableProperty. otoh, XamlC has no idea what your object will be, and require the return type (and the generic interface)

Either returning a string or a binding is a kind of an abuse. I'd indeed use 2 markup extensions.

Lastly, you didn't asked, but... using tons of markup extensions (I guess you have tons for localization) is quite expensive, as a serviceProvider has to be created each time (and passed as argument). As you do not use the serviceProvider, I recommend you to decorate your markup extension with [AcceptEmptyServiceProvider] so the Xaml parser and the Xaml compiler now they can choose to pass null as serviceProvider argument

Another option is to use what we do in Vernacular, attached BindableProperties for translation. that doesn't require any serviceProvider at all.
Comment 5 Philipp Sumi 2017-06-06 12:25:32 UTC
Thanks for the context - that was helpful :)

Yeah, I already switched to a dedicated markup extension. I'd prefer a single one for a framework, but since I'm my only customer, that's just fine! And I didn't know about the attribute - I'll introduce it as soon as I'm switching to 2.3.5.

Thanks!