none
Custom expressions and markup extensions in XAML RRS feed

  • Question

  • Although this question concerns XAML, it does not specifically concern use of XAML in WPF and so I've chosen this forum rather than the WPF specific forum.

    I'm trying to deserialize a set of my own custom classes (not WPF classes) using the XAML serializer. I have my own class framework to serialize, similar to how WPF and WF (Windows Workflow Foundation) have their own class framework and you can serialize/deserialize using the XamlWriter and XamlReader classes.

    The values assigned to properties on these classes need expression support; firstly, because not all of the XAML tree might be loaded when a property is first deserialized by XamlReader (only when all of the XAML tree is loaded) and secondly because the values these expressions resolve to may change over time, after the object is first created. This is similar to how WPF data binding expressions work; when you set a Binding on a property, underneath WPF actually creates an instance of the BindingExpression class and this is stored against the property for the duration of the object's lifetime.

    BindingExpression documentation is here: https://msdn.microsoft.com/en-us/library/system.windows.data.bindingexpression%28v=vs.110%29.aspx?f=255&MSPPError=-2147217396

    Now, I have tried to create my own markup extension and expression object to support the functionality I need. My XAML document:

    <?xml version="1.0" encoding="utf-8" ?>
    <local:TestMarkupObject 
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:local="clr-namespace:TestExpression;assembly=TestExpression"
            MyTestExpressionProperty="{local:TestMarkupExtension Path=mypath}">
    
    </local:TestMarkupObject>

    My test markup object, created as the root object of the XAML:

        public class TestMarkupObject
        {
            public string MyTestExpressionProperty { get; set; }
        }
    
    

    And the test markup extension, as you can see set of the MyTestExpressionProperty in the XAML document above:

        public class TestMarkupExtension : MarkupExtension
        {
            public override object ProvideValue(IServiceProvider serviceProvider)
            {
                IProvideValueTarget target = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
                if (target == null) return null;
    
                TestMarkupObject myobject = target.TargetObject as TestMarkupObject;
                if (myobject != null)
                {
                    return new TestMarkupExpression(myobject, Path);
                }
    
                return null;
            }
    
            public string Path { get; set; }
        }

    And lastly the simple test markup expression code, which this extension returns:

        public class TestMarkupExpression
        {
            public TestMarkupExpression(TestMarkupObject parent, string path)
            {
                Path = path;
            }
    
            public string Path { get; set; }
        }
    
    

    See that the markup extension returns an instance of the TestMarkupExpression containing the path and TestMarkupObject is is set upon.

    All good. Now when I try and deserialize the object using the XamlReader:

    TestMarkupObject markupObject = (TestMarkupObject)XamlReader.Parse(testExpressionXaml);
    
    

    Bang! I get the exception:

    {"Object of type 'TestExpression.TestMarkupExpression' cannot be converted to type 'System.String'."}

    Which is not surprising, as my property is indeed of type 'String' and my markup extension is returning an object of type 'TestMarkupExpression'.

    Here's the clinch. When I look at the underlying BindingExtension class in XAML, I see that this extension returns a value of type 'BindingExpression', not a value of the type of the property it is being set upon - exactly the same as here.

    Why is WPF's BindingExtension allowed to return a property of type BindingExpression with no such problems; and yet in the case of my simple markup extension and expression I am not allowed?

    What is the difference? I have poured over the .NET reference source (dependency properties and dependecy objects) and haven't been able to see the piece of magic that makes the difference. Is there a special WPF XAML reader and writer class that sorts of this problem, and I actually need to create my own custom XAML reader and writer to handle this?

    I apologise for the length of this post; unfortunatley I have found no other way to fully express the question.

    Thank you!


    • Edited by Markos101 Sunday, August 6, 2017 10:42 AM
    Sunday, August 6, 2017 10:38 AM

Answers

  • Hm, how about... System.Windows.Markup.XamlReader.GetWpfSchemaContext returns WpfSharedXamlSchemaContext, which creates WpfXamlType, which creates WpfXamlMember, which creates WpfMemberInvoker, whose SetValue method calls DependencyObject.SetValue. It's a bit misleading that those classes are in the System.Windows.Baml2006 namespace if they apply to loose XAML too.
    • Marked as answer by Markos101 Sunday, August 13, 2017 2:46 PM
    Saturday, August 12, 2017 7:57 PM
  • Thanks again. I noticed the following on MSDN: Default XAML Schema Context And WPF XAML Schema Context regarding the WPF XAML schema context, which says the following:

    Deferral for WPF expressions: WPF features several expression classes that defer a value until a runtime context is available. Also, template expansion is a runtime behavior that relies on deferral techniques.

    Looking at the source code for XAML Schema Context it is possible to define your own (which I assume you can then pass into XamlReader/XamlWriter): XamlSchemaContext because this class is public and can be overridden.

    However, in the interests of simplicity, for now I am simply registering the expression my custom markup extension creates in a cache at the point it is first created rather than going ahead and defining my own custom XAML schema context.

    I know XAML has been written to be extensible, but once you get beyond more simple scenarios I find there is a real lack of documentation or examples. It seems that XAML has been made extensible, but Microsoft have not been forthcoming in producing documentation for those wishing to adopt and extend the standard for their own purposes, possibly deliberately.



    • Edited by Markos101 Sunday, August 13, 2017 2:46 PM
    • Marked as answer by Markos101 Sunday, August 13, 2017 2:47 PM
    Sunday, August 13, 2017 2:45 PM

All replies

  • It seems MS.Internal.ComponentModel.DependencyObjectProvider has a role in that. It provides DPCustomTypeDescriptor, which provides DependencyObjectPropertyDescriptor, whose SetValue method calls DependencyObject.SetValue without going through the corresponding CLR property, so it need not convert the value to the declared type of the property. DependencyObject.SetValueCommon then recognizes the Expression type, from which BindingExpression is indirectly derived.

    Sunday, August 6, 2017 2:07 PM
  • Hi

    What I've done is created a custom type description provider and registered it against my TestMarkupObject using the TypeDescriptionProviderAttribute. In TestMarkupObject, I've added my own SetValue() method and in the type description provider, I've specified this method as the SetValue() override, reflecting the approach in DependencyObjectProvider.

    However, when I re-run the XamlReader.Parse method, I still get the same exception and, when I set a breakpoint in the constructor of my custom type description provider, it is not being called implying that the XamlReader is not calling its methods to get a description of the properties.

    Is there any way I can get XamlReader to recognise and make use of my custom type description provider? I assume that if I registered one, it would use it.

    Saturday, August 12, 2017 11:49 AM
  • Hello. A wee update.

    In short, I don't know why it is OK for the WPF Binding markup extension to return a BindingExpression for any property type. I am assuming now that this is a custom behaviour integrated into the XamlReader/XamlWriter classes - i.e. a custom serializer behaviour for WPF.

    Instead, I have been looking at the following approach: http://putridparrot.com/blog/custom-binding-markupextension/.

    This effectively replicates the WPF Binding class without returning an expression for the value - instead it appears the expression is set on the property using the BindingOperations.SetBinding method, which in the background does the wiring up without the need to actually return an expression as the provided value.

    Saturday, August 12, 2017 4:16 PM
  • Hm, how about... System.Windows.Markup.XamlReader.GetWpfSchemaContext returns WpfSharedXamlSchemaContext, which creates WpfXamlType, which creates WpfXamlMember, which creates WpfMemberInvoker, whose SetValue method calls DependencyObject.SetValue. It's a bit misleading that those classes are in the System.Windows.Baml2006 namespace if they apply to loose XAML too.
    • Marked as answer by Markos101 Sunday, August 13, 2017 2:46 PM
    Saturday, August 12, 2017 7:57 PM
  • Thanks again. I noticed the following on MSDN: Default XAML Schema Context And WPF XAML Schema Context regarding the WPF XAML schema context, which says the following:

    Deferral for WPF expressions: WPF features several expression classes that defer a value until a runtime context is available. Also, template expansion is a runtime behavior that relies on deferral techniques.

    Looking at the source code for XAML Schema Context it is possible to define your own (which I assume you can then pass into XamlReader/XamlWriter): XamlSchemaContext because this class is public and can be overridden.

    However, in the interests of simplicity, for now I am simply registering the expression my custom markup extension creates in a cache at the point it is first created rather than going ahead and defining my own custom XAML schema context.

    I know XAML has been written to be extensible, but once you get beyond more simple scenarios I find there is a real lack of documentation or examples. It seems that XAML has been made extensible, but Microsoft have not been forthcoming in producing documentation for those wishing to adopt and extend the standard for their own purposes, possibly deliberately.



    • Edited by Markos101 Sunday, August 13, 2017 2:46 PM
    • Marked as answer by Markos101 Sunday, August 13, 2017 2:47 PM
    Sunday, August 13, 2017 2:45 PM