问题
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?
回答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.
来源:https://stackoverflow.com/questions/48659674/how-to-insert-a-node-in-a-large-document-using-xmlserializer