Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XmlSerializer: remove unnecessary xsi and xsd namespaces

//Create our own namespaces for the output
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();

//Add an empty namespace and empty value
ns.Add("", "");

//Create the serializer
XmlSerializer slz = new XmlSerializer(someType);

//Serialize the object with our own namespaces (notice the overload)
slz.Serialize(myXmlTextWriter, someObject, ns)

Since Dave asked for me to repeat my answer to Omitting all xsi and xsd namespaces when serializing an object in .NET, I have updated this post and repeated my answer here from the afore-mentioned link. The example used in this answer is the same example used for the other question. What follows is copied, verbatim.


After reading Microsoft's documentation and several solutions online, I have discovered the solution to this problem. It works with both the built-in XmlSerializer and custom XML serialization via IXmlSerialiazble.

To whit, I'll use the same MyTypeWithNamespaces XML sample that's been used in the answers to this question so far.

[XmlRoot("MyTypeWithNamespaces", Namespace="urn:Abracadabra", IsNullable=false)]
public class MyTypeWithNamespaces
{
    // As noted below, per Microsoft's documentation, if the class exposes a public
    // member of type XmlSerializerNamespaces decorated with the 
    // XmlNamespacesDeclarationAttribute, then the XmlSerializer will utilize those
    // namespaces during serialization.
    public MyTypeWithNamespaces( )
    {
        this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
            // Don't do this!! Microsoft's documentation explicitly says it's not supported.
            // It doesn't throw any exceptions, but in my testing, it didn't always work.

            // new XmlQualifiedName(string.Empty, string.Empty),  // And don't do this:
            // new XmlQualifiedName("", "")

            // DO THIS:
            new XmlQualifiedName(string.Empty, "urn:Abracadabra") // Default Namespace
            // Add any other namespaces, with prefixes, here.
        });
    }

    // If you have other constructors, make sure to call the default constructor.
    public MyTypeWithNamespaces(string label, int epoch) : this( )
    {
        this._label = label;
        this._epoch = epoch;
    }

    // An element with a declared namespace different than the namespace
    // of the enclosing type.
    [XmlElement(Namespace="urn:Whoohoo")]
    public string Label
    {
        get { return this._label; }
        set { this._label = value; }
    }
    private string _label;

    // An element whose tag will be the same name as the property name.
    // Also, this element will inherit the namespace of the enclosing type.
    public int Epoch
    {
        get { return this._epoch; }
        set { this._epoch = value; }
    }
    private int _epoch;

    // Per Microsoft's documentation, you can add some public member that
    // returns a XmlSerializerNamespaces object. They use a public field,
    // but that's sloppy. So I'll use a private backed-field with a public
    // getter property. Also, per the documentation, for this to work with
    // the XmlSerializer, decorate it with the XmlNamespaceDeclarations
    // attribute.
    [XmlNamespaceDeclarations]
    public XmlSerializerNamespaces Namespaces
    {
        get { return this._namespaces; }
    }
    private XmlSerializerNamespaces _namespaces;
}

That's all to this class. Now, some objected to having an XmlSerializerNamespaces object somewhere within their classes; but as you can see, I neatly tucked it away in the default constructor and exposed a public property to return the namespaces.

Now, when it comes time to serialize the class, you would use the following code:

MyTypeWithNamespaces myType = new MyTypeWithNamespaces("myLabel", 42);

/******
   OK, I just figured I could do this to make the code shorter, so I commented out the
   below and replaced it with what follows:

// You have to use this constructor in order for the root element to have the right namespaces.
// If you need to do custom serialization of inner objects, you can use a shortened constructor.
XmlSerializer xs = new XmlSerializer(typeof(MyTypeWithNamespaces), new XmlAttributeOverrides(),
    new Type[]{}, new XmlRootAttribute("MyTypeWithNamespaces"), "urn:Abracadabra");

******/

/*****
  Per @dbc, since MyTypeWithNamespaces has a XmlRootAttribute decorating the class,
  You may be able to get away with NOT using this .ctor and use the simple
  XmlSerializer(Type) .ctor.
  Also, be careful not to use serializer creation in loops, as it could lead
  to extensive memory issues due to how serializers are cached (or not...).
  See @dbc's comment and link to SO Q&A for more details.

XmlSerializer xs = new XmlSerializer(typeof(MyTypeWithNamespaces),
    new XmlRootAttribute("MyTypeWithNamespaces") { Namespace="urn:Abracadabra" });
****/
XmlSerializer xs = new XmlSerializer(typeof(MyTypeWithNamespaces));

// I'll use a MemoryStream as my backing store.
MemoryStream ms = new MemoryStream();

// This is extra! If you want to change the settings for the XmlSerializer, you have to create
// a separate XmlWriterSettings object and use the XmlTextWriter.Create(...) factory method.
// So, in this case, I want to omit the XML declaration.
XmlWriterSettings xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
xws.Encoding = Encoding.UTF8; // This is probably the default
// You could use the XmlWriterSetting to set indenting and new line options, but the
// XmlTextWriter class has a much easier method to accomplish that.

// The factory method returns a XmlWriter, not a XmlTextWriter, so cast it.
XmlTextWriter xtw = (XmlTextWriter)XmlTextWriter.Create(ms, xws);
// Then we can set our indenting options (this is, of course, optional).
xtw.Formatting = Formatting.Indented;

// Now serialize our object.
xs.Serialize(xtw, myType, myType.Namespaces);

Once you have done this, you should get the following output:

<MyTypeWithNamespaces>
    <Label xmlns="urn:Whoohoo">myLabel</Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>

I have successfully used this method in a recent project with a deep hierachy of classes that are serialized to XML for web service calls. Microsoft's documentation is not very clear about what to do with the publicly accesible XmlSerializerNamespaces member once you've created it, and so many think it's useless. But by following their documentation and using it in the manner shown above, you can customize how the XmlSerializer generates XML for your classes without resorting to unsupported behavior or "rolling your own" serialization by implementing IXmlSerializable.

It is my hope that this answer will put to rest, once and for all, how to get rid of the standard xsi and xsd namespaces generated by the XmlSerializer.

UPDATE: I just want to make sure I answered the OP's question about removing all namespaces. My code above will work for this; let me show you how. Now, in the example above, you really can't get rid of all namespaces (because there are two namespaces in use). Somewhere in your XML document, you're going to need to have something like xmlns="urn:Abracadabra" xmlns:w="urn:Whoohoo. If the class in the example is part of a larger document, then somewhere above a namespace must be declared for either one of (or both) Abracadbra and Whoohoo. If not, then the element in one or both of the namespaces must be decorated with a prefix of some sort (you can't have two default namespaces, right?). So, for this example, Abracadabra is the default namespace. I could inside my MyTypeWithNamespaces class add a namespace prefix for the Whoohoo namespace like so:

public MyTypeWithNamespaces
{
    this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
        new XmlQualifiedName(string.Empty, "urn:Abracadabra"), // Default Namespace
        new XmlQualifiedName("w", "urn:Whoohoo")
    });
}

Now, in my class definition, I indicated that the <Label/> element is in the namespace "urn:Whoohoo", so I don't need to do anything further. When I now serialize the class using my above serialization code unchanged, this is the output:

<MyTypeWithNamespaces xmlns:w="urn:Whoohoo">
    <w:Label>myLabel</w:Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>

Because <Label> is in a different namespace from the rest of the document, it must, in someway, be "decorated" with a namespace. Notice that there are still no xsi and xsd namespaces.


This ends my answer to the other question. But I wanted to make sure I answered the OP's question about using no namespaces, as I feel I didn't really address it yet. Assume that <Label> is part of the same namespace as the rest of the document, in this case urn:Abracadabra:

<MyTypeWithNamespaces>
    <Label>myLabel<Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>

Your constructor would look as it would in my very first code example, along with the public property to retrieve the default namespace:

// As noted below, per Microsoft's documentation, if the class exposes a public
// member of type XmlSerializerNamespaces decorated with the 
// XmlNamespacesDeclarationAttribute, then the XmlSerializer will utilize those
// namespaces during serialization.
public MyTypeWithNamespaces( )
{
    this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
        new XmlQualifiedName(string.Empty, "urn:Abracadabra") // Default Namespace
    });
}

[XmlNamespaceDeclarations]
public XmlSerializerNamespaces Namespaces
{
    get { return this._namespaces; }
}
private XmlSerializerNamespaces _namespaces;

Then, later, in your code that uses the MyTypeWithNamespaces object to serialize it, you would call it as I did above:

MyTypeWithNamespaces myType = new MyTypeWithNamespaces("myLabel", 42);

XmlSerializer xs = new XmlSerializer(typeof(MyTypeWithNamespaces),
    new XmlRootAttribute("MyTypeWithNamespaces") { Namespace="urn:Abracadabra" });

...

// Above, you'd setup your XmlTextWriter.

// Now serialize our object.
xs.Serialize(xtw, myType, myType.Namespaces);

And the XmlSerializer would spit back out the same XML as shown immediately above with no additional namespaces in the output:

<MyTypeWithNamespaces>
    <Label>myLabel<Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>

There is an alternative - you can provide a member of type XmlSerializerNamespaces in the type to be serialized. Decorate it with the XmlNamespaceDeclarations attribute. Add the namespace prefixes and URIs to that member. Then, any serialization that does not explicitly provide an XmlSerializerNamespaces will use the namespace prefix+URI pairs you have put into your type.

Example code, suppose this is your type:

[XmlRoot(Namespace = "urn:mycompany.2009")]
public class Person {
  [XmlAttribute] 
  public bool Known;
  [XmlElement]
  public string Name;
  [XmlNamespaceDeclarations]
  public XmlSerializerNamespaces xmlns;
}

You can do this:

var p = new Person
  { 
      Name = "Charley",
      Known = false, 
      xmlns = new XmlSerializerNamespaces()
  }
p.xmlns.Add("",""); // default namespace is emoty
p.xmlns.Add("c", "urn:mycompany.2009");

And that will mean that any serialization of that instance that does not specify its own set of prefix+URI pairs will use the "p" prefix for the "urn:mycompany.2009" namespace. It will also omit the xsi and xsd namespaces.

The difference here is that you are adding the XmlSerializerNamespaces to the type itself, rather than employing it explicitly on a call to XmlSerializer.Serialize(). This means that if an instance of your type is serialized by code you do not own (for example in a webservices stack), and that code does not explicitly provide a XmlSerializerNamespaces, that serializer will use the namespaces provided in the instance.


I'm using:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        const string DEFAULT_NAMESPACE = "http://www.something.org/schema";
        var serializer = new XmlSerializer(typeof(Person), DEFAULT_NAMESPACE);
        var namespaces = new XmlSerializerNamespaces();
        namespaces.Add("", DEFAULT_NAMESPACE);

        using (var stream = new MemoryStream())
        {
            var someone = new Person
            {
                FirstName = "Donald",
                LastName = "Duck"
            };
            serializer.Serialize(stream, someone, namespaces);
            stream.Position = 0;
            using (var reader = new StreamReader(stream))
            {
                Console.WriteLine(reader.ReadToEnd());
            }
        }
    }
}

To get the following XML:

<?xml version="1.0"?>
<Person xmlns="http://www.something.org/schema">
  <FirstName>Donald</FirstName>
  <LastName>Duck</LastName>
</Person>

If you don't want the namespace, just set DEFAULT_NAMESPACE to "".