Я написал несколько тестов для чтения файла XML и проверки его по схеме XSD. Мои объекты данных используют сочетание основанной на атрибутах и пользовательской реализации IXmlSerializable, и я использую XmlSerializer для выполнения десериализации.
Мой тест включает вставку неизвестного элемента в XML, чтобы он не соответствовал схемы. Затем я проверяю, запускается ли событие проверки.
Если неизвестный элемент помещен в XML, значит, он является дочерним по отношению к одному из классов данных на основе атрибутов (т. Е. Свойства украшены атрибутами XmlAttribute и XmlElement) , тогда проверка запускается правильно.
Если, однако, неизвестный элемент помещен в XML, так что он является дочерним по отношению к одному из классов IXmlSerializable, тогда генерируется исключение System.InvalidOperationException, но проверка по-прежнему выполняется fire.
Код внутри ReadXmlElements пользовательской коллекции создает новый XmlSerializer для чтения в дочерних элементах, это вызов Deserialize, в котором генерируется исключение InvalidOperationException.
Если я помещаю попытку .. блокировать блокировку вокруг этого вызова, он застревает в бесконечной л oop. Похоже, единственное решение состоит в том, чтобы поместить блок try-catch вокруг вызова XmlSerializer.Deserialize верхнего уровня (как показано в тесте).
Кто-нибудь знает, почему XmlSerializer ведет себя таким образом? В идеале я хотел бы попытаться поймать исключение, где оно генерируется, вместо того, чтобы иметь обработчик исключений верхнего уровня, поэтому возникает вторичный вопрос о том, почему код застревает в бесконечном l oop, если попытаться .. Блок catch добавлен в класс коллекции.
Вот исключение, которое выдается:
System.InvalidOperationException: There is an error in XML document (13, 10). ---> System.InvalidOperationException: There is an error in XML document (13, 10). ---> System.InvalidOperationException: <UnknownElement xmlns='example'> was not expected.
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderGroup.Read1_Group()
--- End of inner exception stack trace ---
at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader)
at XmlSerializerTest.EntityCollection~1.ReadXmlElements(XmlReader reader) in C:\source\repos\XmlSerializerTest\XmlSerializerTest\EntityCollection.cs:line 55
at XmlSerializerTest.EntityCollection~1.ReadXml(XmlReader reader) in C:\Users\NGGMN9O\source\repos\XmlSerializerTest\XmlSerializerTest\EntityCollection.cs:line 41
at System.Xml.Serialization.XmlSerializationReader.ReadSerializable(IXmlSerializable serializable, Boolean wrappedAny)
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderExample.Read2_Example(Boolean isNullable, Boolean checkType)
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationReaderExample.Read3_Example()
--- End of inner exception stack trace ---
at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader, String encodingStyle, XmlDeserializationEvents events)
at System.Xml.Serialization.XmlSerializer.Deserialize(XmlReader xmlReader)
at XmlSerializerTest.StackOverflowExample.InvalidElementInGroupTest() in C:\source\repos\XmlSerializerTest\XmlSerializerTest\XmlSerializerTest.cs:line 35
Schema.xsd
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema xmlns:local="example"
attributeFormDefault="unqualified"
elementFormDefault="qualified"
targetNamespace="example"
version="1.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- Attribute Groups -->
<xs:attributeGroup name="Identifiers">
<xs:attribute name="Id"
type="xs:string"
use="required" />
<xs:attribute name="Name"
type="xs:string"
use="required" />
</xs:attributeGroup>
<!-- Complex Types -->
<xs:complexType abstract="true"
name="Entity">
<xs:sequence>
<xs:element name="Description"
type="xs:string"
minOccurs="0"
maxOccurs="1" />
</xs:sequence>
<xs:attributeGroup ref="local:Identifiers" />
</xs:complexType>
<xs:complexType name="DerivedEntity">
<xs:complexContent>
<xs:extension base="local:Entity">
<xs:attribute name="Parameter"
use="required" />
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="Groups">
<xs:sequence>
<xs:element name="Group" type="local:Group" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="Group">
<xs:complexContent>
<xs:extension base="local:Entity">
<xs:sequence>
<xs:element name="DerivedEntity"
type="local:DerivedEntity"
minOccurs="0"
maxOccurs="unbounded" />
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<!-- Main Schema Definition -->
<xs:element name="Example">
<xs:complexType>
<xs:sequence>
<xs:element name="Groups"
type="local:Groups"
minOccurs="1"
maxOccurs="1" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
InvalidElementInGroup. xml
<?xml version="1.0"?>
<Example xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="example">
<Groups>
<Group Name="abc" Id="123">
<DerivedEntity Id="123" Name="xyz" Parameter="ijk">
<Description>def</Description>
</DerivedEntity>
<DerivedEntity Id="234" Name="bob" Parameter="12"/>
</Group>
<Group Name="def" Id="124">
<Description>This is a description.</Description>
</Group>
<UnknownElement/>
</Groups>
</Example>
Реализация Примечание: Код, показанный в этом примере, не является производственный код. Я знаю, что мог бы просто использовать реализацию List<T>
, которая поддерживает сериализацию, без необходимости реализации IXmlSerializable.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
namespace XmlSerializerTest
{
public class Example
{
public Example()
{
Groups = new Groups();
}
public Groups Groups { get; set; }
}
public class Groups : EntityCollection<Group>
{
}
public class Group : Entity, IXmlSerializable
{
private EntityCollection<DerivedEntity> entityCollection;
public Group()
{
this.entityCollection = new EntityCollection<DerivedEntity>();
}
#region IXmlSerializable Implementation
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
// Read the attributes
ReadXmlAttributes(reader);
// Consume the start element
bool isEmptyElement = reader.IsEmptyElement;
reader.ReadStartElement();
if (!isEmptyElement)
{
ReadXmlElements(reader);
reader.ReadEndElement();
}
}
/// <summary>
/// Reads the XML elements.
/// </summary>
/// <param name="reader">The reader.</param>
public override void ReadXmlElements(XmlReader reader)
{
// Handle the optional base class description element
base.ReadXmlElements(reader);
entityCollection.ReadXmlElements(reader);
}
public void WriteXml(XmlWriter writer)
{
throw new NotImplementedException();
}
#endregion
}
public class EntityCollection<T> : IXmlSerializable, IList<T> where T : Entity
{
private List<T> childEntityField;
public EntityCollection()
{
childEntityField = new List<T>();
}
#region IXmlSerializable Implementation
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
reader.MoveToContent();
// Read the attributes
ReadXmlAttributes(reader);
// Consume the start element
bool isEmptyElement = reader.IsEmptyElement;
reader.ReadStartElement();
if (!isEmptyElement)
{
ReadXmlElements(reader);
reader.ReadEndElement();
}
}
public virtual void ReadXmlAttributes(XmlReader reader)
{
}
public virtual void ReadXmlElements(XmlReader reader)
{
XmlSerializer deserializer = new XmlSerializer(typeof(T), "example");
while (reader.IsStartElement())
{
T item = (T)deserializer.Deserialize(reader); // throws an InvalidOperationException if an unknown element is encountered.
if (item != null)
{
Add(item);
}
}
}
public void WriteXml(XmlWriter writer)
{
throw new NotImplementedException();
}
#endregion
#region IList Implementation
public IEnumerator<T> GetEnumerator()
{
return childEntityField.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)childEntityField).GetEnumerator();
}
public void Add(T item)
{
childEntityField.Add(item);
}
public void Clear()
{
childEntityField.Clear();
}
public bool Contains(T item)
{
return childEntityField.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
childEntityField.CopyTo(array, arrayIndex);
}
public bool Remove(T item)
{
return childEntityField.Remove(item);
}
public int Count => childEntityField.Count;
public bool IsReadOnly => ((ICollection<T>)childEntityField).IsReadOnly;
public int IndexOf(T item)
{
return childEntityField.IndexOf(item);
}
public void Insert(int index, T item)
{
childEntityField.Insert(index, item);
}
public void RemoveAt(int index)
{
childEntityField.RemoveAt(index);
}
public T this[int index]
{
get => childEntityField[index];
set => childEntityField[index] = value;
}
#endregion
}
[System.Xml.Serialization.XmlIncludeAttribute(typeof(DerivedEntity))]
public abstract class Entity
{
public string Description { get; set; }
public string Id { get; set; }
public string Name { get; set; }
public virtual void ReadXmlAttributes(XmlReader reader)
{
Id = reader.GetAttribute("Id");
Name = reader.GetAttribute("Name");
}
public virtual void ReadXmlElements(XmlReader reader)
{
if (reader.IsStartElement("Description"))
{
Description = reader.ReadElementContentAsString();
}
}
}
public class DerivedEntity : Entity
{
public string Parameter { get; set; }
}
}
Тест
namespace XmlSerializerTest
{
using System;
using System.IO;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class StackOverflowExample
{
[TestMethod]
[DeploymentItem(@"Schema.xsd")]
[DeploymentItem(@"InvalidElementInGroup.xml")]
public void InvalidElementInGroupTest()
{
// Open the file
FileStream stream = new FileStream("InvalidElementInGroup.xml", FileMode.Open);
// Configure settings
XmlReaderSettings settings = new XmlReaderSettings();
settings.Schemas.Add(null, @"Schema.xsd");
settings.ValidationType = ValidationType.Schema;
settings.ValidationEventHandler += OnValidationEvent;
XmlSerializer xmlDeserializer = new XmlSerializer(typeof(Example), "example");
// Deserialize from the stream
stream.Position = 0;
XmlReader xmlReader = XmlReader.Create(stream, settings);
try
{
Example deserializedObject = (Example)xmlDeserializer.Deserialize(xmlReader);
}
catch (Exception e)
{
Console.WriteLine("Exception: " + e);
}
}
private void OnValidationEvent(object sender, ValidationEventArgs e)
{
Console.WriteLine("Validation Event: " + e.Message);
}
}
}