在使用数据协定序列化程序序列化IXmlSerializable对象时,如何控制根元素名称空间和名称?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在使用数据协定序列化程序序列化IXmlSerializable对象时,如何控制根元素名称空间和名称?相关的知识,希望对你有一定的参考价值。

我有一个实现IXmlSerializable的类型,我用DataContractSerializer序列化。在将其作为XML文档的根元素序列化时,如何控制根元素名称空间和名称?

说我有以下类型:

public partial class PersonDTO : IXmlSerializable
{
    public string Name { get; set; }

    #region IXmlSerializable Members

    public System.Xml.Schema.XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(System.Xml.XmlReader reader)
    {
        Name = reader["name"];
        if (!reader.IsEmptyElement)
            reader.Skip();
        reader.Read();
    }

    public void WriteXml(System.Xml.XmlWriter writer)
    {
        writer.WriteAttributeString("name", Name);
    }

    #endregion
}

如果我用DataContractSerializer作为我的根对象来序列化这个,我得到:

<PersonDTO name="John Doe" xmlns="http://schemas.datacontract.org/2004/07/MyClrNamespace" />

我希望根名称为<Person>,根名称空间为"http://www.MyCompany.com",所以我尝试添加[DataContract],如下所示:

[DataContract(Name = "Person", Namespace = "http://www.MyCompany.com")]
public partial class PersonDTO : IXmlSerializable
{
}

但是当我这样做时,DataContractSerializer抛出一个异常,说明类型'PersonDTO'不能是IXmlSerializable并且具有DataContractAttribute属性:

System.Runtime.Serialization.InvalidDataContractException occurred
  Message="Type 'PersonDTO' cannot be IXmlSerializable and have DataContractAttribute attribute."
  Source="System.Runtime.Serialization"
  StackTrace:
       at System.Runtime.Serialization.XmlDataContract.XmlDataContractCriticalHelper..ctor(Type type)
       at System.Runtime.Serialization.XmlDataContract..ctor(Type type)
       at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.CreateDataContract(Int32 id, RuntimeTypeHandle typeHandle, Type type)
       at System.Runtime.Serialization.DataContract.DataContractCriticalHelper.GetDataContractSkipValidation(Int32 id, RuntimeTypeHandle typeHandle, Type type)
       at System.Runtime.Serialization.DataContract.GetDataContract(RuntimeTypeHandle typeHandle, Type type, SerializationMode mode)
       at System.Runtime.Serialization.DataContractSerializer.get_RootContract()

我知道在手动序列化时可以使用DataContractSerializer(Type type, String rootName, String rootNamespace)构造函数修改根名称和命名空间:

var person = new PersonDTO { Name = "John Doe", };

var serializer = new DataContractSerializer(typeof(PersonDTO), "Person", @"http://www.MyCompany.com");
var sb = new StringBuilder();
using (var textWriter = new StringWriter(sb))
using (var xmlWriter = XmlWriter.Create(textWriter))
{
    serializer.WriteObject(xmlWriter, person);
}
Console.WriteLine(sb);
// Outputs <Person name="John Doe" xmlns="http://www.MyCompany.com" />

但有没有办法通过属性自动执行此操作?

答案

这可以使用两种方式之一的属性来完成。

首先(并且令人惊讶地)如果您将旧[XmlRoot]XmlSerializer属性应用于该类型,DataContractSerializer将使用其中指定的命名空间和名称作为根数据协定命名空间和名称:

[XmlRoot("Person", Namespace = "http://www.MyCompany.com")]
public partial class PersonDTO : IXmlSerializable
{
}

这会生成以下XML:

<Person name="John Doe" xmlns="http://www.MyCompany.com" />

但是,此解决方案仅适用于根元素名称。如果您尝试序列化此类对象的数组或通用列表,则使用未修改的命名空间和名称:

<ArrayOfPersonDTO xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/MyClrNamespace">
  <PersonDTO name="John Doe" />
</ArrayOfPersonDTO>

其次,更强大的是,[XmlSchemaProvider]属性可用于指定一个静态方法,该方法返回类型的数据协定名称,名称空间和模式:

[XmlSchemaProvider("GetSchemaMethod")]
public partial class PersonDTO : IXmlSerializable
{
    // This is the method named by the XmlSchemaProviderAttribute applied to the type.
    public static XmlQualifiedName GetSchemaMethod(XmlSchemaSet xs)
    {
        // Fill in a plausible schema for the type if necessary.
        // 
        // While DataContractSerializer will not use the returned schema set, 
        // svcutil.exe will use it to generate schemas.  XmlSerializer also
        // seems to require it to be initialized to something plausible if you
        // are serializing your types with both serializers.
        string personSchema = @"<xs:schema xmlns:tns=""http://www.MyCompany.com"" elementFormDefault=""qualified"" targetNamespace=""http://www.MyCompany.com"" xmlns:xs=""http://www.w3.org/2001/XMLSchema"">
  <xs:element name=""Person"" nillable=""true"" type=""tns:Person"" />
  <xs:complexType name=""Person"">
    <xs:attribute name=""name"" type=""xs:string"" />
  </xs:complexType>
</xs:schema>";
        using (var textReader = new StringReader(personSchema))
        using (var schemaSetReader = System.Xml.XmlReader.Create(textReader))
        {
            xs.Add("http://www.MyCompany.com", schemaSetReader);
        }
        // Return back the namespace and name to be used for this type.
        return new XmlQualifiedName("Person", "http://www.MyCompany.com");
    }
}

这样做的好处是,不仅可以修改根名称和命名空间,还可以使用数组,泛型集合和其他泛型中使用的数据协定名称:

<ArrayOfPerson xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.MyCompany.com">
  <Person name="John Doe" />
</ArrayOfPerson>

笔记:

  • DataContractSerializer仅使用模式提供程序方法返回的XmlQualifiedName。但是,如果您计划使用svcutil.exe为您的类型生成XSD,或者使用XmlSerializer序列化您的类型,则需要使用合理的东西填写XmlSchemaSet xs。 (当你这样做时,生成的XSD将反映返回的模式。)

以上是关于在使用数据协定序列化程序序列化IXmlSerializable对象时,如何控制根元素名称空间和名称?的主要内容,如果未能解决你的问题,请参考以下文章

创建 WCF 代理时数据协定未标记为可序列化

使用 DataContractSerializer 时设置属性的初始值

RPC序列化方式优缺点

WCF反序列化如何在不调用构造函数的情况下实例化对象?

使用 DataContractSerializer 从 WCF 服务接收到的序列化响应

序列化为 JSON 字符串时如何修复 OutOfMemoryException?