65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.35/5 (16投票s)

2017年6月19日

CPOL

5分钟阅读

viewsIcon

25724

downloadIcon

296

关于反序列化器和序列化器的基本概念;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);
}

我试图向您展示如何找出问题以及如何快速修复它。查找源代码和完整的项目。它已附加。

© . All rights reserved.