Reputation: 6896
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
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:
Read
method we read once from the underlying readerXamlReader.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 procedureThat 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
Reputation: 6896
Looks like what I need to use is the DontThrowOnErrors
flag in the new System.Xaml.XamlReaderSettings
class in .NET 4.0.
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
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