DataContractSerializer with Multiple Namespaces

扶醉桌前 提交于 2019-12-08 17:24:36

问题


I am using a DataContractSerializer to serialize an object to XML. The main object is SecurityHolding with the namespace "http://personaltrading.test.com/" and contains a property called Amount that's a class with the namespace "http://core.test.com". When I serialize this to XML I get the following:

<ArrayOfSecurityHolding xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://personaltrading.test.com/">
  <SecurityHolding>
    <Amount xmlns:d3p1="http://core.test.com/">
        <d3p1:Amount>1.05</d3p1:Amount>
        <d3p1:CurrencyCode>USD</d3p1:CurrencyCode>
    </Amount>
    <BrokerageID>0</BrokerageID>
    <BrokerageName i:nil="true" />
    <RecordID>3681</RecordID>
  </SecurityHolding></ArrayOfSecurityHolding>

Is there anyway I can control the d3p1 prefix? Am I doing something wrong or should I be doing something else?


回答1:


Firstly, the choice of namespace alias should make no difference to a well-formed parser.

But; does it have to be DataContractSerializer? With XmlSerializer, you can use the overload of Serialize that accepts a XmlSerializerNamespaces. This allows you to pick and choose the namespaces and aliases that you use.

Ultimately; DataContractSerializer is not intended to give full xml control; that isn't its aim. If you want strict xml control, XmlSerializer is a better choice, even if it is older (and has some nuances/foibles of its own).

Full example:

using System;
using System.Xml.Serialization;
public class Amount
{
    public const string CoreNamespace = "http://core.test.com/";
    [XmlElement("Amount", Namespace=CoreNamespace)]
    public decimal Value { get; set; }
    [XmlElement("CurrencyCode", Namespace = CoreNamespace)]
    public string Currency { get; set; }
}
[XmlType("SecurityHolding", Namespace = SecurityHolding.TradingNamespace)]
public class SecurityHolding
{
    public const string TradingNamespace = "http://personaltrading.test.com/";

    [XmlElement("Amount", Namespace = Amount.CoreNamespace)]
    public Amount Amount { get; set; }

    public int BrokerageId { get; set; }
    public string BrokerageName { get; set; }
    public int RecordId { get; set; }
}
static class Program
{
    static void Main()
    {
        var data = new[] {
            new SecurityHolding {
                Amount = new Amount {
                    Value = 1.05M,
                    Currency = "USD"
                },
                BrokerageId = 0,
                BrokerageName = null,
                RecordId = 3681
            }
        };
        var ser = new XmlSerializer(data.GetType(),
            new XmlRootAttribute("ArrayOfSecurityHolding") { Namespace = SecurityHolding.TradingNamespace});
        var ns = new XmlSerializerNamespaces();
        ns.Add("foo", Amount.CoreNamespace);
        ser.Serialize(Console.Out, data, ns);
    }
}

Output:

<ArrayOfSecurityHolding xmlns:foo="http://core.test.com/" xmlns="http://personaltrading.test.com/">
  <SecurityHolding>
    <foo:Amount>
      <foo:Amount>1.05</foo:Amount>
      <foo:CurrencyCode>USD</foo:CurrencyCode>
    </foo:Amount>
    <BrokerageId>0</BrokerageId>
    <RecordId>3681</RecordId>
  </SecurityHolding>
</ArrayOfSecurityHolding>



回答2:


I have solved this problem slightly differently to Marc that can be implemented in a base class.

  1. Create a new attribute to define the additional XML namespaces that you will use in your data contract.

    [AttributeUsage(AttributeTargets.Class, Inherited = true, AllowMultiple = true)]    
    public sealed class NamespaceAttribute : Attribute    
    {   
    
        public NamespaceAttribute()
        {
        }
    
        public NamespaceAttribute(string prefix, string uri)
        {
            Prefix = prefix;
            Uri = uri;
        }
    
        public string Prefix { get; set; }
        public string Uri { get; set; }
    }
    
  2. Add the attribute to your data contracts.

    [DataContract(Name = "SomeObject", Namespace = "http://schemas.domain.com/namespace/")]    
    [Namespace(Prefix = "a", Uri = "http://schemas.microsoft.com/2003/10/Serialization/Arrays")]    
    [Namespace(Prefix = "wm", Uri = "http://schemas.datacontract.org/2004/07/System.Windows.Media")]           
    public class SomeObject : SerializableObject          
    {    
    
        private IList<Color> colors;
    
        [DataMember]
        [DisplayName("Colors")]
        public IList<Colors> Colors
        {
            get { return colors; }
            set { colours = value; }
        }
    }
    
  3. Then in your Save method, use reflection to get the attributes and then write them to the file.

    public static void Save(SerializableObject o, string filename)
    {
        using (Stream outputStream = new FileStream(filename, FileMode.Create, FileAccess.Write))
        {
            if (outputStream == null)
                throw new ArgumentNullException("Must have valid output stream");
    
            if (outputStream.CanWrite == false)
                throw new ArgumentException("Cannot write to output stream");
    
            object[] attributes;
            attributes = o.GetType().GetCustomAttributes(typeof(NamespaceAttribute), true);    
    
            XmlWriterSettings writerSettings = new XmlWriterSettings();                
            writerSettings.Indent = true;
            writerSettings.NewLineOnAttributes = true;                
            using (XmlWriter w = XmlWriter.Create(outputStream, writerSettings))
            {
                DataContractSerializer s = new DataContractSerializer(o.GetType());
    
                s.WriteStartObject(w, o);
                foreach (NamespaceAttribute ns in attributes)                      
                    w.WriteAttributeString("xmlns", ns.Prefix, null, ns.Uri);
    
                // content
                s.WriteObjectContent(w, o);
                s.WriteEndObject(w);
            }
        }
    }
    



回答3:


I have struggled with this problem also. The solution I present below is not optimal IMHO but it works. Like Marc Gravell above, I suggest using XmlSerializer.

The trick is to add a field to your class that returns a XmlSerializerNamespaces object. This field must be decorated with a XmlNamespaceDeclarations attribute. In the constructor of your class, add namespaces as shown in the example below. In the xml below note that the root element is prefixed correctly as well as the someString element.

More info on XmlSerializerNamespaces

Schemas reference

[XmlRoot(Namespace="http://STPMonitor.myDomain.com")]
public class CFMessage : IQueueMessage<CFQueueItem>
{
    [XmlNamespaceDeclarations]
    public XmlSerializerNamespaces xmlns;

    [XmlAttribute("schemaLocation", Namespace=System.Xml.Schema.XmlSchema.InstanceNamespace)]
    public string schemaLocation = "http://STPMonitor.myDomain.com/schemas/CFMessage.xsd";

    [XmlAttribute("type")]
    public string Type { get; set; }

    [XmlAttribute("username")]
    public string UserName { get; set; }

    [XmlAttribute("somestring", Namespace = "http://someURI.com")]
    public string SomeString = "Hello World";


    public List<CFQueueItem> QueueItems { get; set; }

    public CFMessage()
    {
        xmlns = new XmlSerializerNamespaces();
        xmlns.Add("myDomain", "http://STPMonitor.myDomain.com");
        xmlns.Add("xyz", "http://someURI.com");
    }
}


<?xml version="1.0" encoding="utf-16"?>
<myDomain:CFMessage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xyz="http://someURI.com"
xsi:schemaLocation="http://STPMonitor.myDomain.com/schemas/CFMessage.xsd"
xyz:somestring="Hello World" type="JOIN" username="SJ-3-3008-1"
xmlns:myDomain="http://STPMonitor.myDomain.com" />



回答4:


Add "http://www.w3.org/2001/XMLSchema" namespace by:

    private static string DataContractSerialize(object obj)
    {
        StringWriter sw = new StringWriter();

        DataContractSerializer serializer = new DataContractSerializer(obj.GetType());

        using (XmlTextWriter xw = new XmlTextWriter(sw))
        {
            //serializer.WriteObject(xw, obj);
            //
            // Insert namespace for C# types
            serializer.WriteStartObject(xw, obj);
            xw.WriteAttributeString("xmlns", "x", null, "http://www.w3.org/2001/XMLSchema");
            serializer.WriteObjectContent(xw, obj);
            serializer.WriteEndObject(xw);
        }

        StringBuilder buffer = sw.GetStringBuilder();

        return buffer.ToString();
    }


来源:https://stackoverflow.com/questions/1744120/datacontractserializer-with-multiple-namespaces

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!