2

I have a large XML document and I want to use the XmlSerializer class to insert new elements whose content comes from a .NET class instance generated using xsd.exe.

This is a follow-up to the question How to deserialize a node in a large document using XmlSerializer, and uses the same xsd and generated classes that are described in that question.

Let's say that in my sample XML I want to exchange my Ford car for a BMW. I've tried the following code:

static string XmlContent = @"
    <RootNode xmlns=""http://MyNamespace"">
        <Cars>
        <Car make=""Volkswagen"" />
        <Car make=""Ford"" />
        <Car make=""Opel"" />
        </Cars>
    </RootNode>";

private static void TestWriteMcve()
{
    var doc = new XmlDocument();
    doc.LoadXml(XmlContent);
    var nsMgr = new XmlNamespaceManager(doc.NameTable);
    nsMgr.AddNamespace("myns", "http://MyNamespace");

    var node = doc.DocumentElement.SelectSingleNode("myns:Cars/myns:Car[@make='Ford']", nsMgr);
    var parent = node.ParentNode;
    var carSerializer = new XmlSerializer(typeof(Car));

    using (var writer = node.CreateNavigator().InsertAfter())
    {
        // WriteWhitespace needed to avoid error "WriteStartDocument cannot
        // be called on writers created with ConformanceLevel.Fragment."
        writer.WriteWhitespace("");
        var newCar = new Car { make = "BMW" };
        carSerializer.Serialize(writer, newCar);
    }
    parent.RemoveChild(node);
    Console.WriteLine(parent.OuterXml);
}

The result I get is close to what I want:

<Cars xmlns="http://MyNamespace">
  <Car make="Volkswagen" />
  <Car xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" make="BMW" xmlns="" />
  <Car make="Opel" />
</Cars>

except for all those unwanted xmlns:... attributes on the element that was added. Where did they come from and how can I get rid of them?

dbc
  • 104,963
  • 20
  • 228
  • 340
Joe
  • 122,218
  • 32
  • 205
  • 338

1 Answers1

1

As explained in Omitting all xsi and xsd namespaces when serializing an object in .NET?, XmlSerializer will always helpfully add the xsi and xsd namespaces when serializing. If you don't want that, you need to call an overload of Serialize where the required initial namespaces can be specified, e.g. XmlSerializer.Serialize(XmlWriter, Object, XmlSerializerNamespaces). The following extension method does the trick:

public static class XmlNodeExtensions
{
    public static XmlNode ReplaceWithSerializationOf<T>(this XmlNode node, T replacement)
    {
        if (node == null)
            throw new ArgumentNullException();
        var parent = node.ParentNode;
        var serializer = new XmlSerializer(replacement == null ? typeof(T) : replacement.GetType());

        using (var writer = node.CreateNavigator().InsertAfter())
        {
            // WriteWhitespace needed to avoid error "WriteStartDocument cannot
            // be called on writers created with ConformanceLevel.Fragment."
            writer.WriteWhitespace("");

            // Set up an appropriate initial namespace.
            XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
            ns.Add(node.GetNamespaceOfPrefix(node.NamespaceURI), node.NamespaceURI);

            // Serialize
            serializer.Serialize(writer, replacement, ns);
        }

        var nextNode = node.NextSibling;
        parent.RemoveChild(node);
        return nextNode;
    }
}

Then use it as follows:

var newCar = new Car { make = "BMW" };
var node = doc.DocumentElement.SelectSingleNode("myns:Cars/myns:Car[@make='Ford']", nsMgr);
node = node.ReplaceWithSerializationOf(newCar);
var parent = node.ParentNode;

Afterwards, the XML generated for doc will be:

<RootNode xmlns="http://MyNamespace">
  <Cars>
    <Car make="Volkswagen" />
    <Car make="BMW" />
    <Car make="Opel" />
  </Cars>
</RootNode>

Sample working .Net fiddle.

dbc
  • 104,963
  • 20
  • 228
  • 340
  • That's great thanks. I'd assumed that the attributes added to the auto-generated classes by xsd.exe would be enough. – Joe Feb 08 '18 at 07:44
  • @Joe - the XML generated using the auto-generated classes is perfectly valid and well-formed. It's just less compact that you would like. – dbc Feb 08 '18 at 07:47
  • This is an excellent answer, as is your answer to the linked related question. I particularly appreciate that you went to the trouble of creating a working .Net fiddle. – Joe Feb 08 '18 at 20:17