DataContractSerializer with Multiple Namespaces
Asked Answered
F

4

8

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?

Fetus answered 16/11, 2009 at 18:55 Comment(0)
C
9

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>
Cabana answered 16/11, 2009 at 20:24 Comment(1)
"the choice of namespace alias should make no difference to a well-formed parser". If you could you please share information that with the large German ERP software maker so they stop making weird xml like <ns0:Something xmlns:ns0='muhnamespace'>... required, I would be in your debt.Ume
K
5

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);
            }
        }
    }
    
Kanara answered 4/7, 2011 at 16:36 Comment(0)
C
1

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" />
Chisholm answered 18/9, 2012 at 17:28 Comment(0)
A
0

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();
    }
Aggress answered 15/1, 2013 at 9:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.