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

时间:2009-09-30 19:31:38

标签: c# xaml serialization

我正在使用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 个答案:

答案 0 :(得分:1)

如果您希望代码处理旧属性,那么您将不得不明确地捕获异常,然后继续读取文件。

通过更改数据结构,您使旧的XAML无效,并且解析器非常正确地反对。

答案 1 :(得分:1)

事实证明,完成起来并不像乍看起来那样困难。这里的关键信息是,提到的异常不是由XamlReader引发的,而是由XamlObjectWriter引发的,它负责消耗XamlReader并创建和填充结果对象。因此,我们所需要做的就是提供一个定制的XamlReader,它将简单地跳过未知属性。我认为最通用的方法是创建一个读者,该读者将包裹另一个(任意)读者。这个想法可以总结如下:

  • Read方法中,我们从基础阅读器读取了一次
  • 如果遇到未知属性,只要在XamlReader.Member.IsUnknownXamlReader.NodeType时通过检查StartMember即可轻松确定,我们将继续读取直到达到成员定义的末尾( strong>对应的 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节点

答案 2 :(得分:0)

看起来我需要使用的是.NET 4.0中新DontThrowOnErrors类中的System.Xaml.XamlReaderSettings标志。

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

不幸的是,该属性没有进入.NET 4的最终版本,因此没有简单的方法来执行此操作