在 .NET 中实现 XML 序列化和数据契约序列化工具






3.35/5 (16投票s)
关于反序列化器和序列化器的基本概念;XmlSerializer 和 DataContractSerializer 的优缺点;实现 XML 和 DataContract 序列化器/反序列化器。
在 .NET 中实现 XML 序列化和数据契约序列化工具
主题读者 - 开发者
覆盖主题
- 关于反序列化和序列化的基本概念
- XML 序列化的优缺点
- XML 序列化实现
- XML 序列化的白盒测试
- 数据契约序列化的优缺点
- 问题分析
- 可排序数据契约序列化实现
- 数据契约序列化的白盒测试
让我们深入了解基本概念
上面的图表想表达的是:
- 反序列化:将 XML 字符串转换为对象。
- 序列化:将对象转换为 XML 字符串。
XmlSerializer
- 只有公共属性可以被转换。
- 它可以序列化继承自 IEnumerable 和 ICollection 的集合。
- 您不需要为每个需要转换的属性指定序列化属性。如果您想忽略某个属性不进行转换,则必须使用 [XmlIgnore] 属性。
局限性
- 它只能序列化属性
- 属性应具有 set 和 get 功能。它不能序列化私有或只读属性、索引器和方法。
类图
项目创建
问题可以用多种不同的方式解决。目前,我主要关注 XML 序列化和数据契约序列化。
基本概念
我在项目中使用了以下引用来进行 XML 和数据契约序列化:
- System.Xml.Serialization
- System.Runtime.Serialization
我还使用了 System.IO 命名空间。我们需要以下类:
- MemoryStream
- StreamWriter
- StreamReader
MemoryStream 类
- 它在内存 (RAM) 中存储数据
- 它用于快速临时存储
StreamWriter 和 StreamReader
这两个类都用于读写基于字符的数据。
使用 StreamWriter 进行 Flush
Flush 用于将缓冲区中的信息移动到目标。
XML 序列化实现
毫无疑问,这些代码是没问题的。它可以将对象序列化为 XML 字符串。您可以毫无问题地使用这些代码。
现在我们来看使用 using 块的另一种实现。
GetType 与 typeof
看看上面的图片,我使用了 using 块,这在此实现中不是主要的改变。看标有红色的第 1 项,我使用了 tag.GetType() 而不是 typeof(T)。使用 GetType 更好,因为它更安全。
在图片中的第一个实现中,当我调用 serializer.Serialize(streamWriter, tag) 时,有时 XmlSerializer 会因为 typeof(T) 而抛出运行时异常。
如果您因为使用 typeof(T) 而没有遇到错误,那么您可能会有不同的看法。无论如何,我将使用 GetType 而不是 typeof(T)。
优点
- 由于使用了 using 块,Dispose 方法会自动调用。您无需手动调用它。
- 即使我没有使用 StreamWriter.Flush(第一个实现的代码中标记为 2)。
XML 反序列化实现
看看上面的图片;我做了一些很小的改动。您可以使用其中任何一种实现。因此,您可以使用这些反序列化方法将 XML 字符串转换为对象。
XML 序列化器/反序列化器的白盒测试
对于白盒测试,我将使用行为驱动开发 (BDD) 作为测试方法的命名约定。
BDD 概念
1. **给定** 我是 BDD 技术的新手,我以前从未使用过这项技术
2. **当** 我阅读了关于 BDD 的这篇教程
3. **那么** 我开始喜欢它,最终学会了它。
注意:我避免了 BDD 的细节,以保持简洁。
XML 反序列化器测试
我正在编写一个方法来测试反序列化器方法。
测试方法的命名约定
给定_有效的_Person_XML_字符串_当_调用反序列化器_那么_应返回有效的_Person_对象()
假设我有一个 XML 字符串,其中包含一个人的姓名和地址。
<?xml version='1.0' encoding='utf-16'?> <Person> <PersonId>044373a4-0f17-4ec4-8e1f-ac6d1d7873e7</PersonId> <LastName>Rony</LastName> <FirstName>HR</FirstName> <Address> <AddressId>b90c16b4-418a-4eca-80fb-8679a60b418e</AddressId> <City>City</City> <State>State</State> <ZipCode>1200</ZipCode> </Address> </Person>
完整的测试方法见下文,它应该能通过。
[TestMethod] public void Given_Valid_XML_String_For_Person_When_DeSerializer_Is_Called_Then_It_Should_Return_Valid_Person_Object() { //// Arrange Person personObj; //// Positive Test Test Data string xmlData = @"<?xml version='1.0' encoding='utf-16'?> <Person> <PersonId>044373a4-0f17-4ec4-8e1f-ac6d1d7873e7</PersonId> <LastName>Rony</LastName> <FirstName>HR</FirstName> <Address> <AddressId>b90c16b4-418a-4eca-80fb-8679a60b418e</AddressId> <City>City</City> <State>State</State> <ZipCode>1200</ZipCode> </Address> </Person>"; string expectedLastNamet = "Rony"; //// Act personObj = xmlSerializerUtility.DeSerializer<Person>(xmlData); //// Assert. Assert.AreEqual(personObj.LastName, expectedLastNamet); }
XML 序列化器测试
serialize 方法在测试方法内部调用,它应该能通过。
[TestMethod] public void Given_Valid_PersonObj_When_Serialize_Is_Called_Then_It_Should_Return_Valid_XML_Data() { //// Arrange Address address = new Address { AddressId = Guid.NewGuid(), City = "City", State = "State", ZipCode = "1200" }; Person person = new Person { PersonId = Guid.NewGuid(), FirstName = "HR", LastName = "Rony", Address = address }; //// Expected Result. string actualXmlData = null; //// Act actualXmlData = xmlSerializerUtility.Serializer<person>(person); //// Assert. Assert.IsNotNull(actualXmlData); } </person>
问题:我正在实现一个微服务。假设有一个客户端接口,它以 XML 格式发送数据到服务。我无法控制客户端接口。以某种方式,我弄清楚了模型(实体)类所需的元素/属性。在分析完 XML 和实体类之后,我遇到了以下问题:
- 服务将收到具有未排序元素的 XML 字符串。
- 现有的模型类包含 IDictionary 或 IList。
我只关注 XML 和数据契约反序列化器/序列化器。
DataContractSerializer
- 公共属性和私有字段可以被转换。但您必须加上 [DataMember] 属性。
- 它不支持 XML 属性
- 它支持 IList、IDictionary(HashTable)接口。
局限性
- XML 必须按字母顺序排序。
性能:通常比 XmlSerializer 快 10%。
DataContract 序列化器实现
看看上面的代码,它与之前的实现几乎相同,除了 DataContractSerializer 类。
现在假设我有一个 XML 字符串,其中包含一个 Employee 的姓名和教育属性。
<Employee> <LastName>Rony</LastName> <FirstName>HR</FirstName> <EmployeeId>d0d54690-037f-473b-9eff-4e30e8e0ed4f</EmployeeId> <Educations> <Education> <EducationId>748a5d33-2cda-454f-ab23-63f12ecccd76</EducationId> <EmployeeId>d0d54690-037f-473b-9eff-4e30e8e0ed4f</EmployeeId> <DegreeText>Bachelors in computer science</DegreeText> </Education> <Education> <EducationId>9491fdbb-e1e8-4781-bb61-749e837a0b11</EducationId> <EmployeeId>d0d54690-037f-473b-9eff-4e30e8e0ed4f</EmployeeId> <DegreeText>Masters in computer science</DegreeText> </Education> </Educations> </Employee>
看看 XML 字符串中元素(LastName, FirstName, EmployeeId)的标签,它们没有按字母顺序排序。
问题分析
同样,如果您查看 employee 的模型类,您会发现,在运行时,它是按字母顺序排序的。
所以,如果我运行测试,我将得到无效的对象数据。
看看上面的对象,它只获取了“LastName”的值。因为在 employee 模型类中,它首先获取 LastName,然后才是 FastName。所以,它从 XML 中获取 LastName 的值,然后忽略 FirstName 和其他值的。
解决方案
- 对 XML 元素进行排序。
- 也删除空的标签。
我添加了两个扩展方法用于对 XML 字符串进行排序和删除空元素。
internal static class XmlSorter { internal static void Sort(this XElement xElement, bool sortAttributes = true) { //// Make sure there is a valid xElement if (xElement == null) throw new ArgumentNullException("XElement is null"); //// Sort attributes if needed if (sortAttributes) { List <XAttribute> sortedAttributes = xElement.Attributes().OrderBy(a => a.ToString(), new CaseSensitiveComparer()).ToList(); sortedAttributes.ForEach(a => a.Remove()); sortedAttributes.ForEach(a => xElement.Add(a)); } //// Sort the children if anything exist List <XElement> sortedChildren = xElement.Elements().OrderBy(e => e.Name.ToString(), new CaseSensitiveComparer()).ToList(); if (xElement.HasElements) { xElement.RemoveNodes(); sortedChildren.ForEach(c => c.Sort(sortAttributes)); sortedChildren.ForEach(c => xElement.Add(c)); } } internal static void RemoveEmptyElement(this XElement xElement) { //// Make sure there is a valid xElement if (xElement == null) throw new ArgumentNullException("XElement is null"); //// Remove Empty/Blanks elements in collection of XML nodes. xElement.Descendants().Where(e => string.IsNullOrEmpty(e.Value)).Remove(); } } internal class CaseSensitiveComparer : IComparer <string> { public int Compare(string x, string y) { return string.Compare(x, y, StringComparison.Ordinal); } }
可排序 DataContract 序列化器
看看上面的代码,我包装了之前实现的 DataContractSerializerUtility 类。我调用了 RemoveEmptyElements 扩展方法来从 XML 字符串中删除空的标签。
删除空标签后,它通过调用 sort 扩展方法对 XML 字符串的标签进行排序。
DataContract 序列化器/反序列化器的白盒测试
现在如果我运行以下测试,它将通过。现在这些方法都可以使用了。
反序列化测试
[TestMethod] public void Given_Valid_XML_Data_For_Employee_When_DeSerializer_Is_Called_Then_It_Should_Return_Valid_Employee_Object() { //// Arrange ISerialization sortabledataContractSerializer = new DataContractSortableSerializerUtility(dataContractSerializer); //// Valid Test Data Employee employeeObj; string xmlData = @" <Employee> <LastName>Rony</LastName> <FirstName>HR</FirstName> <EmployeeId>d0d54690-037f-473b-9eff-4e30e8e0ed4f</EmployeeId> <Educations> <Education> <DegreeText>Bachelors in computer science</DegreeText> <EducationId>748a5d33-2cda-454f-ab23-63f12ecccd76</EducationId> <EmployeeId>d0d54690-037f-473b-9eff-4e30e8e0ed4f</EmployeeId> </Education> <Education> <DegreeText>Masters in computer science</DegreeText> <EducationId>9491fdbb-e1e8-4781-bb61-749e837a0b11</EducationId> <EmployeeId>d0d54690-037f-473b-9eff-4e30e8e0ed4f</EmployeeId> </Education> </Educations> </Employee>"; int expectedCount = 2; //// Act employeeObj = sortabledataContractSerializer.DeSerializer<Employee>(xmlData); //// Assert. Assert.AreEqual(employeeObj.Educations.Count, expectedCount); }
测试的预期结果如下:
序列化测试
[TestMethod] public void Given_Valid_EmployeeObj_When_Serialize_Is_Called_Then_It_Should_Return_Valid_XML_Data() { //// Arrange ISerialization sortabledataContractSerializer = new DataContractSortableSerializerUtility(dataContractSerializer); //// Valid Test Data Employee employee = new Employee(); employee.EmployeeId = Guid.NewGuid(); employee.FirstName = "HR"; employee.LastName = "Rony"; List<Education> educationList = new List<Education>(); Education education = new Education(); education.EducationId = Guid.NewGuid(); education.EmployeeId = employee.EmployeeId; education.DegreeText = "Bachelors in computer science"; educationList.Add(education); Education educationObj2 = new Education(); educationObj2.EducationId = Guid.NewGuid(); educationObj2.EmployeeId = employee.EmployeeId; educationObj2.DegreeText = "Masters in computer science"; educationList.Add(educationObj2); employee.Educations = educationList; //// Expected Result. string actualXmlData = null; //// Act actualXmlData = sortabledataContractSerializer.Serializer<Employee>(employee); //// Assert. Assert.IsNotNull(actualXmlData); }
我试图向您展示如何找出问题以及如何快速修复它。查找源代码和完整的项目。它已附加。