问题
I'm trying to make an Xml file that looks something like:
<RootLevel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.MyCompany.com/MySchema.xsd">
<Level1>
<Level2>
</Level2>
</Level1 >
<Level1>
<Level2>
</Level2>
</Level1 >
etc. repeats hundreds of times
</RootLevel>
I generated some classes from my xml schema definition file using the xsd.exe utility. They look like:
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://MyCompany.com/MySchema.xsd")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://www.MyCompany.com/MySchema.xsd", IsNullable = false)]
public partial class RootLevel
{
private List<Level1> level1Field;
public List<Level1> Level1Field
{
get { return this.level1Field;}
set {this.level1Field = value;}
}
}
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.MyCompany.com/MySchema.xsd")]
public partial class Level1
{
private List<Level2> level2Field;
public List<Level2> Level2Field
{
get { return this.level2Field;}
set {this.level2Field = value;}
}
/* other properties on Level1 go here*/
}
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.MyCompany.com/MySchema.xsd")]
public partial class Level2
{
/* properties on Level2 go here*/
}
I'm making this file by writing out the RootLevel element using XmlWriter.WriteStartElement() and then I write out the rest of the file by creating Level1 objects and serializing them with XmlSerializer.
Goal
I want the file to only have the namespace on the RootLevel element.
In case you are interested, here is what I tried so far:
Starting Point
At the start, my RootLevel element did not have any namespaces. My Level1 and Level2 elements had namespaces.
Step 1:
I tried removing the namespaces from Level1 and Level2 elements by overriding the namespaces on the XmlTypeAttributes for classes Level1 and Level2.
XmlTypeAttribute attribute = new XmlTypeAttribute();
attribute.Namespace = string.Empty;
XmlAttributes attributes = new XmlAttributes();
attributes.XmlType = attribute;
XmlAttributeOverrides overrides = new XmlAttributeOverrides();
overrides.Add(typeof(Level1), attributes);
overrides.Add(typeof(Level2), attributes);
this.xmlSerializer = new XmlSerializer(typeof(Level1), overrides);
Step 1 Result
The namespace was removed from Level2 but not from Level1.
Step 2
Added some more code to try to remove the namespace from Level1. I tried using the namespaces parameter of the XmlSerializer.Serialize() method to use empty namespaces. Note "level1" is an object of type "Level1".
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add("", "");
this.xmlSerializer.Serialize(this.xmlFileWriter, level1, namespaces);
Step 2 Result
The namespace is removed from both Level1 and Level2. So far, so good! Now all I need to do is add the namespace stuff to RootLevel.
Step 3
Since RootLevel is not serialized, I added some XmlWriter code to try adding the namespaces to RootLevel.
string defaultNamespace = "http://www.MyCompany.com/MySchema.xsd";
this.xmlFileWriter.WriteStartElement("RootLevel", defaultNamespace);
this.xmlFileWriter.WriteAttributeString("xmlns", "xsi", "", "http://www.w3.org/2001/XMLSchema-instance");
this.xmlFileWriter.WriteAttributeString("xmlns", "xsd", "", "http://www.w3.org/2001/XMLSchema");
Step 3 Result
The namespaces were added to RootLevel. Yay!. But, now every Level1 element has an xmlns="" attribute. Grrr!
<RootLevel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.MyCompany.com/MySchema.xsd">
<Level1 xmlns="">
<Level2>
</Level2>
</Level1 >
<Level1 xmlns="">
<Level2>
</Level2>
</Level1 >
etc. repeats hundreds of times
</RootLevel>
So why did that happen?
回答1:
You didn't post your original code so I don't know exactly where you went wrong, but I was able to get the XML you want as follows:
Extract a static helper class
XmlNamespaces
to hold namespace strings and declare them asconst
strings. In your question you sometimes use"http://MyCompany.com/MySchema.xsd"
but sometimes"http://www.MyCompany.com/MySchema.xsd"
. This might just be a typo in the question, but if your code uses these inconsistently this will cause bugs.Apply
XmlRootAttribute
to the classLevel1
in addition toXmlTypeAttribute
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = XmlNamespaces.Default)] [System.Xml.Serialization.XmlRootAttribute(Namespace = XmlNamespaces.Default, IsNullable = false)] public partial class Level1 { // Properties }
When writing the root element, pass the default namespace string to XmlWriter.WriteStartElement(string, string).
Overall code is as follows:
public static class XmlNamespaces
{
public const string Default = "http://MyCompany.com/MySchema.xsd";
public const string xsi = "http://www.w3.org/2001/XMLSchema-instance";
public const string xsd = "http://www.w3.org/2001/XMLSchema";
public static XmlSerializerNamespaces XmlSerializerNamespaces
{
get
{
var namespaces = new XmlSerializerNamespaces();
namespaces.Add("", XmlNamespaces.Default);
namespaces.Add("xsi", XmlNamespaces.xsi);
namespaces.Add("xsd", XmlNamespaces.xsd);
return namespaces;
}
}
}
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = XmlNamespaces.Default)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = XmlNamespaces.Default, IsNullable = false)] // Added this and used const string from XmlNamespaces class
public partial class Level1
{
private List<Level2> level2Field;
public List<Level2> Level2Field
{
get { return this.level2Field; }
set { this.level2Field = value; }
}
/* other properties on Level1 go here*/
}
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = XmlNamespaces.Default)] // Used const string from XmlNamespaces class
public partial class Level2
{
/* properties on Level2 go here*/
}
public static class XmlSerializationUtilities
{
public static void WriteList<TItem>(string rootName, XmlSerializerNamespaces namespaces, IEnumerable<TItem> list, TextWriter textWriter)
{
var namespaceList = namespaces.ToArray();
string defaultNamespace = null;
foreach (var ns in namespaceList)
{
if (string.IsNullOrEmpty(ns.Name))
{
defaultNamespace = ns.Namespace;
break;
}
}
var settings = new XmlWriterSettings();
settings.Indent = true;
settings.IndentChars = " ";
using (var writer = XmlWriter.Create(textWriter, settings))
{
writer.WriteStartDocument();
writer.WriteStartElement(rootName, defaultNamespace);
foreach (var ns in namespaceList)
if (!string.IsNullOrEmpty(ns.Name))
writer.WriteAttributeString("xmlns", ns.Name, null, ns.Namespace);
var serializer = new XmlSerializer(typeof(TItem));
foreach (var item in list)
{
serializer.Serialize(writer, item);
}
writer.WriteEndElement();
writer.WriteEndDocument();
}
}
}
public static class TestClass
{
public static void Test()
{
var list = new List<Level1> { new Level1 { Level2Field = new List<Level2> { new Level2(), new Level2() } }, new Level1 { Level2Field = new List<Level2> { new Level2(), new Level2(), new Level2(), new Level2() } } };
string xml;
using (var writer = new StringWriter())
{
XmlSerializationUtilities.WriteList("RootLevel", XmlNamespaces.XmlSerializerNamespaces, list, writer);
xml = writer.ToString();
Debug.WriteLine(xml);
}
}
}
And the output is
<?xml version="1.0" encoding="utf-16"?>
<RootLevel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://MyCompany.com/MySchema.xsd">
<Level1>
<Level2Field>
<Level2 />
<Level2 />
</Level2Field>
</Level1>
<Level1>
<Level2Field>
<Level2 />
<Level2 />
<Level2 />
<Level2 />
</Level2Field>
</Level1>
</RootLevel>
来源:https://stackoverflow.com/questions/28571394/serialize-part-of-xml-file-want-namespace-on-root-not-on-serialized-subelement