Search code examples
c#.netxmlxsdxml-serialization

Change autogenerated class' XmlSerialization's namespace handling


I'm currently in the process to create a bunch of files that should then be used by a different programm. Most of them are XML-Files. Naturally, I extracted the .xsd files from the program and used the xsd.exe tool to autogenerate C#-classes, which works rather well.

The problem

Serializing the autogenerated class results in a xml file like this:

<root xmlns="ns1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <group xmlns="">
        <item>foo</item>
        <item>bar</item>
    </group>
</root>

Both xsi and xsd aren't used in anything that I have found so far, but that shouldn't really be a problem.

The XML that the program would expect would look something like this:

<n1:root xmlns:n1="ns1">
    <group>
        <item>foo</item>
        <item>bar</item>
    </group>
</n1:root

Both xml should result in the same thing when deserialized, so I do not put the fault onto xsd.exe.

However, when trying to open the generated xml in the program, it produces an "Object reference not set to an instance of an object" error. Both the xmlns:xsi and xmlns:xsd have to be removed and it has to use xmlns:n1 instead of the default namespace.

What I have tried

At first I thought, I could just manually serialize the class using IXmlSerializable, however that produces a runtime error when serializing, since xsd.exe automatically adds XmlTypeAttribute and XmlRootAttribute.

The produced error reads InvalidOperationException: Only XmlRoot attribute may be specified for the type myNs.MyClass. Please use XmlSchemaProviderAttribute to specify schema type.

I don't think using XmlSchemaProviderAttribute is a good idea, because that defeats the idea of autogenerating the class from the given schema. (The schema is probably gonna change in future versions of the program)


If you want a minimal example, here is some code that runs on rextester.com: (note that rextester uses .NET framework 4.5, while I am using .NET framework 4.7, so any answers using new features are more than welcome)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace Rextester
{
    // AUTOGENERATED
    [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.7.2558.0")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.somens.com")]
    [System.Xml.Serialization.XmlRootAttribute(Namespace="http://www.somens.com", IsNullable=false)]
    public partial class Test {
        [System.Xml.Serialization.XmlArrayAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
        [System.Xml.Serialization.XmlArrayItemAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=false)]
        public Item[] Group { get; set; }
    }

    [System.CodeDom.Compiler.GeneratedCodeAttribute("xsd", "4.7.2558.0")]
    [System.SerializableAttribute()]
    [System.Diagnostics.DebuggerStepThroughAttribute()]
    [System.ComponentModel.DesignerCategoryAttribute("code")]
    [System.Xml.Serialization.XmlTypeAttribute(Namespace="http://www.somens.com")]
    public partial class Item {
        [System.Xml.Serialization.XmlAttributeAttribute()]
        public int Value { get; set; }
    }

    // CUSTOM
    public partial class Test : IXmlSerializable
    {
        public XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(XmlReader reader)
        {
            throw new NotImplementedException();
        }

        public void WriteXml(XmlWriter writer)
        {
            foreach (var item in Group) {
                writer.WriteStartElement("item");
                writer.WriteAttributeString("Value", item.Value.ToString());
                writer.WriteEndElement();
            }
        }

    }


    public class Program
    {
        public static void Main(string[] args)
        {
            var t = new Test();
            t.Group = new Item[] { new Item { Value = 5}, new Item { Value = 10} };

            var serializer = new XmlSerializer(typeof(Test));
            serializer.Serialize(Console.Out, t);
        }
    }
}

Solution

  • I might be missing the point entirely because I don't know what you mean by "extracted the .xsd files from the program" nor do I know what kind of integration you are writing.

    If your end goal is simply to get the namespacing right so the program will eat your data, ditch the all of the auto-generated XmlSerialier attributes and tell the class and the serializer how you want things to look. Sometimes less is more when it comes to the serializer and it will do a lot of work on its own if you let it.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text.RegularExpressions;
    using System.Xml;
    using System.Xml.Schema;
    using System.Xml.Serialization;
    
    namespace Rextester
    {
    
        [XmlRoot("root", Namespace = "ns1")]
        public partial class Test 
        {
            [XmlArray(ElementName = "Group", Namespace = "")]
            public Item[] Group { get; set; }
        }
    
        public partial class Item
        {
            public int Value { get; set; }
        }
    
         public class Program
        {
            public static void Main(string[] args)
            {
                var t = new Test();
                t.Group = new Item[] { new Item { Value = 5 }, new Item { Value = 10 } };
                XmlSerializerNamespaces xsn = new XmlSerializerNamespaces();
                xsn.Add("n1", "ns1");
                var serializer = new XmlSerializer(typeof(Test));
                serializer.Serialize(Console.Out, t,xsn);
            }
        }
    }
    

    Which outputs.

    <?xml version="1.0" encoding="IBM437"?>
    <n1:root xmlns:n1="ns1">
      <Group>
        <Item>
          <Value>5</Value>
        </Item>
        <Item>
          <Value>10</Value>
        </Item>
      </Group>
    </n1:root>
    

    This doesn't look exactly like your example, but with some munging of your class you could combine the Item and Value tags.