Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
424 views
in Technique[技术] by (71.8m points)

c# - How can I control the root element namespace and name when serializing an IXmlSerializable object with the data contract serializer?

I have a type that implements IXmlSerializable which I am serializing with DataContractSerializer. How can I control the root element namespace and name when serializing it as the root element of an XML document?

Say I have the following type:

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
}

If I serialize this with DataContractSerializer as my root object I get:

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

I want the root name to be <Person> and the root namespace to be "http://www.MyCompany.com", so I tried adding [DataContract] like so:

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

But when I do, DataContractSerializer throws an exception stating Type 'PersonDTO' cannot be IXmlSerializable and have DataContractAttribute attribute:

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()

I know it is possible to modify the root name and namespace by using the DataContractSerializer(Type type,?String rootName,?String rootNamespace) constructor when serializing manually:

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" />

But is there any way to do this automatically via attributes?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

This can be done using attributes in one of two ways.

Firstly (and surprisingly) if you apply the [XmlRoot] attribute for the old XmlSerializer to the type, DataContractSerializer will use the namespace and name specified therein as the root data contract namespace and name:

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

Which generates the following XML:

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

However, this solution only applies to the root element name. If you try to serialize an array or generic list of such objects the unmodified namespace and name are used:

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

Secondly and more powerfully, the [XmlSchemaProvider] attribute can be used to specify a static method that returns a data contract name, namespace and schema for the type:

[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");
    }
}

This has the advantage that not only will the root name and namespace be modified, but also the data contract name used in arrays, generic collections, and other generics will be as well:

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

Notes:

  • DataContractSerializer uses only the XmlQualifiedName returned by the schema provider method. However, if you plan to generate an XSD for your type using svcutil.exe or also serialize your type with XmlSerializer, you will need to fill in the XmlSchemaSet xs with something plausible. (And when you do, the XSD generated will reflect the returned schema.)

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...