Bryan Legend
Bryan Legend

Reputation: 6896

How to get XamlReader.Parse to not throw "property does not exist" XamlParseException?

I'm using XAML for serializing some objects and it's working great for the most part.

The problem I'm now facing is when I change the data structure all the old objects produce an exception like the one below. I don't mind if the values are lost.

Is there a way to turn off these exceptions and just have the xaml reader ignore unknown properties? If there isn't a way to do this now, is there maybe something in the new System.Xaml namespace that could do it?

System.Windows.Markup.XamlParseException: The property 'BorderPadding' does not exist in XML namespace 'clr-namespace:TemplateGenerator;assembly=App_Code'. Line '1' Position '158'.
  at System.Windows.Markup.XamlParser.ThrowExceptionWithLine(String message, Int32 lineNumber, Int32 linePosition)
  at System.Windows.Markup.XamlParser.ThrowException(String id, String value1, String value2, Int32 lineNumber, Int32 linePosition)
  at System.Windows.Markup.XamlParser.WriteUnknownAttribute(XamlUnknownAttributeNode xamlUnknownAttributeNode)
  at System.Windows.Markup.XamlParser.ProcessXamlNode(XamlNode xamlNode, Boolean& cleanup, Boolean& done)
  at System.Windows.Markup.XamlParser.ReadXaml(Boolean singleRecordMode)
  at System.Windows.Markup.TreeBuilderXamlTranslator._Parse()
  at System.Windows.Markup.XamlParser.Parse()
  at System.Windows.Markup.XamlTreeBuilder.ParseFragment()
  at System.Windows.Markup.TreeBuilder.Parse()
  at System.Windows.Markup.XamlReader.XmlTreeBuildDefault(ParserContext pc, XmlReader reader, Boolean wrapWithMarkupCompatReader, XamlParseMode parseMode, Boolean etwTracingEnabled)
  at System.Windows.Markup.XamlReader.Load(XmlReader reader)
  at System.Windows.Markup.XamlReader.Parse(String xamlText)

Upvotes: 3

Views: 2581

Answers (3)

Grx70
Grx70

Reputation: 10349

Turns out it's not as hard to accomplish as it may seem at first. The key information here is that the mentioned exception is thrown not by a XamlReader, but rather a XamlObjectWriter, which is responsible for consuming a XamlReader and creating and populating a resulting object. So all we need to do is to provide a customized XamlReader that would simply skip over unknown properties. The most versatile approach in my opinion is to create a reader that would wrap around another (arbitrary) reader. The idea can be summarized as follows:

  • In the Read method we read once from the underlying reader
  • If we encounter unknown property, which can be easily determined by examining XamlReader.Member.IsUnknown whenever XamlReader.NodeType is StartMember, we simply continue reading until we reach the end of the member definition (a corresponding1 EndMember node) and move to next node by reading one more time; if the next node is also an unknown property, we repeat the procedure

That way a single call to Read will skip over unknown properties, possibly resulting in multiple reads form the underlying reader, but that behavior will be transparent to the consumer.

Here's an example code:

public class LaxXamlReader : XamlReader
{
    public override bool Read()
    {
        //Read once from the underlying reader
        _Reader.Read();

        //Check if current node is an unknown property
        while (NodeType == XamlNodeType.StartMember && Member.IsUnknown)
        {
            //We need to track member nesting level so that we can correctly
            //identify the corresponding EndMember node
            var level = 1;
            while (level > 0)
            {
                _Reader.Read();
                if (NodeType == XamlNodeType.StartMember)
                    level++;
                else if (NodeType == XamlNodeType.EndMember)
                    level--;
            }

            //At this point we're at the corresponsing EndMember node, so we
            //advance to the next node; if it's also an unknown property, it
            //will be caught by the while loop
            _Reader.Read();
        }

        //If we've reached the end of input return false
        return !IsEof;
    }

    public override XamlReader ReadSubtree()
        => new LaxXamlReader(_Reader.ReadSubtree());

    protected override void Dispose(bool disposing)
    {
        //Only dispose the underlying reader if Dispose() was called;
        //otherwise let GC do the job
        if (disposing)
            ((IDisposable)_Reader).Dispose();
        base.Dispose(disposing);
    }

    //The code below simply forwards the functionality from the underlying reader

    public LaxXamlReader(XamlReader reader)
    {
        _Reader = reader;
    }

    private readonly XamlReader _Reader;
    public override bool IsEof => _Reader.IsEof;
    public override XamlMember Member => _Reader.Member;
    public override NamespaceDeclaration Namespace => _Reader.Namespace;
    public override XamlNodeType NodeType => _Reader.NodeType;
    public override XamlSchemaContext SchemaContext => _Reader.SchemaContext;
    public override XamlType Type => _Reader.Type;
    public override object Value => _Reader.Value;
    public override void Skip() => _Reader.Skip();
}

Usage example:

var xaml = "<Object Foo=\"Bar\" xmlns=\"clr-namespace:System;assembly=mscorlib\" />";
var obj = XamlServices.Load(new LaxXamlReader(new XamlXmlReader(new StringReader(xaml))));

Note that XamlReader (along with other mentioned XAML related types) is the one defined in System.Xaml namespace.


1 Since properties in XAML can be written as elements and contain objects with their own properties, we need to ignore EndMember nodes corresponding to these nested properties

Upvotes: 4

Bryan Legend
Bryan Legend

Reputation: 6896

Looks like what I need to use is the DontThrowOnErrors flag in the new System.Xaml.XamlReaderSettings class in .NET 4.0.

See http://msdn.microsoft.com/en-us/library/system.xaml.xamlreadersettings.dontthrowonerrors%28VS.100%29.aspx

Unfortunately that property did not make it into the final version of .NET 4, so there is no easy way to do this.

Upvotes: 0

ChrisF
ChrisF

Reputation: 137148

If you want your code to cope with the old attributes then you're going to have to trap the exception explicitly and then carry on reading the file.

By changing the data structure you've made the old XAML invalid and the parser is quite rightly objecting.

Upvotes: 1

Related Questions