【问题标题】:How can I control the root element namespace and name when serializing an IXmlSerializable object with the data contract serializer?使用数据协定序列化程序序列化 IXmlSerializable 对象时,如何控制根元素命名空间和名称?
【发布时间】:2017-12-18 02:00:01
【问题描述】:

我有一个实现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" />

我希望根名称为&lt;Person&gt;,根命名空间为"http://www.MyCompany.com",所以我尝试像这样添加[DataContract]

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

但是当我这样做时,DataContractSerializer 会抛出一个异常,指出 Type '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" />

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

【问题讨论】:

    标签: c# xml wcf datacontractserializer ixmlserializable


    【解决方案1】:

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

    首先(令人惊讶的是)如果您将旧 XmlSerializer[XmlRoot] 属性应用于该类型,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 将反映返回的架构。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-30
      • 1970-01-01
      • 2013-04-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多