如何使XamlReader.Parse不抛出“属性不存在”的XamlParseException?

3
我正在使用XAML对一些对象进行序列化,大多数情况下都运行良好。
现在遇到的问题是,当我改变数据结构时,所有旧对象都会产生如下异常。尽管失去这些值我并不介意。
有没有一种方法可以关闭这些异常,让XAML阅读器忽略未知属性?如果现在无法做到这一点,那么新的System.Xaml命名空间中可能有什么东西可以实现吗?
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)
3个回答

2
原来实现这个并没有想象中的那么难。关键信息是,提到的异常不是由 XamlReader 抛出的,而是由负责消费 XamlReader 并创建和填充结果对象的 XamlObjectWriter 抛出的。因此,我们需要做的就是提供一个定制的 XamlReader,它只会跳过未知的属性。在我看来,最通用的方法是创建一个读取器,它会包装另一个(任意的)读取器。这个想法可以总结如下:
  • Read 方法中,我们从底层读取器中读取一次
  • 如果我们遇到未知的属性,可以通过检查 XamlReader.NodeTypeStartMemberXamlReader.Member.IsUnknown 来轻松确定,我们只需继续读取,直到达到成员定义的结尾(一个相应的1 EndMember 节点),然后通过再读取一次来移动到下一个节点;如果下一个节点也是未知的属性,我们就重复这个过程
这样,对 Read 的单个调用将跳过未知的属性,可能导致从底层读取器中进行多次读取,但这种行为对消费者来说是透明的。
以下是示例代码:
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();
}

使用示例:

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

请注意,XamlReader(以及其他提到的与XAML相关的类型)是在System.Xaml命名空间中定义的。
1由于XAML中的属性可以被写成元素,并包含具有自己属性的对象,因此我们需要忽略对应于这些嵌套属性的EndMember节点。

这将是我的答案。装饰器模式是我首先想到的允许不存在的功能,它还提供了灵活性来处理特定的异常,同时允许其他任何东西按预期抛出。 - Josh Gust

1

如果您希望您的代码能够处理旧属性,那么您需要显式地捕获异常,然后继续读取文件。

通过更改数据结构,您使旧的XAML无效,因此解析器会合理地反对。


0

1
显然,该属性没有包含在 .net 4.0 的发布版本中。 - Bryan Legend

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接