在.NET 中序列化对象时省略所有 xsi 和 xsd 名称空间?

代码如下:

StringBuilder builder = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
using (XmlWriter xmlWriter = XmlWriter.Create(builder, settings))
{
XmlSerializer s = new XmlSerializer(objectToSerialize.GetType());
s.Serialize(xmlWriter, objectToSerialize);
}

生成的序列化文档包含名称空间,如下所示:

<message xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"
xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"
xmlns="urn:something">
...
</message>

要删除 xsi 和 xsd 名称空间,可以按照 如何在不获得 xmlns = “ ...”的情况下将对象序列化为 XML?给出的答案。

我希望我的消息标记为 <message>(没有任何名称空间属性)?

134924 次浏览

这是我对这个问题的两个回答中的第一个。

如果希望对名称空间进行良好的控制——例如,如果希望省略其中一些名称空间,而不是其他名称空间,或者如果希望用另一个名称空间替换一个名称空间,则可以使用 XmlAttributeOverride完成此操作。

假设您有这样的类型定义:

// explicitly specify a namespace for this type,
// to be used during XML serialization.
[XmlRoot(Namespace="urn:Abracadabra")]
public class MyTypeWithNamespaces
{
// private fields backing the properties
private int _Epoch;
private string _Label;


// explicitly define a distinct namespace for this element
[XmlElement(Namespace="urn:Whoohoo")]
public string Label
{
set {  _Label= value; }
get { return _Label; }
}


// this property will be implicitly serialized to XML using the
// member name for the element name, and inheriting the namespace from
// the type.
public int Epoch
{
set {  _Epoch= value; }
get { return _Epoch; }
}
}

这个序列化伪代码:

        var o2= new MyTypeWithNamespaces() { ..initializers...};
ns.Add( "", "urn:Abracadabra" );
XmlSerializer s2 = new XmlSerializer(typeof(MyTypeWithNamespaces));
s2.Serialize(System.Console.Out, o2, ns);

您将得到类似于这样的 XML:

<MyTypeWithNamespaces xmlns="urn:Abracadabra">
<Label xmlns="urn:Whoohoo">Cimsswybclaeqjh</Label>
<Epoch>97</Epoch>
</MyTypeWithNamespaces>

请注意,根元素上有一个默认名称空间,“ Label”元素上也有一个不同的名称空间。在上面的代码中,这些命名空间是由装饰类型的属性指定的。

中的 Xml 序列化框架。NET 包括显式 重写装饰实际代码的属性的可能性。您可以使用 XmlAttributesOverride 类和好友来完成此操作。假设我有相同的类型,我这样序列化它:

        // instantiate the container for all attribute overrides
XmlAttributeOverrides xOver = new XmlAttributeOverrides();


// define a set of XML attributes to apply to the root element
XmlAttributes xAttrs1 = new XmlAttributes();


// define an XmlRoot element (as if [XmlRoot] had decorated the type)
// The namespace in the attribute override is the empty string.
XmlRootAttribute xRoot = new XmlRootAttribute() { Namespace = ""};


// add that XmlRoot element to the container of attributes
xAttrs1.XmlRoot= xRoot;


// add that bunch of attributes to the container holding all overrides
xOver.Add(typeof(MyTypeWithNamespaces), xAttrs1);


// create another set of XML Attributes
XmlAttributes xAttrs2 = new XmlAttributes();


// define an XmlElement attribute, for a type of "String", with no namespace
var xElt = new XmlElementAttribute(typeof(String)) { Namespace = ""};


// add that XmlElement attribute to the 2nd bunch of attributes
xAttrs2.XmlElements.Add(xElt);


// add that bunch of attributes to the container for the type, and
// specifically apply that bunch to the "Label" property on the type.
xOver.Add(typeof(MyTypeWithNamespaces), "Label", xAttrs2);


// instantiate a serializer with the overrides
XmlSerializer s3 = new XmlSerializer(typeof(MyTypeWithNamespaces), xOver);


// serialize
s3.Serialize(System.Console.Out, o2, ns2);

结果是这样的:

<MyTypeWithNamespaces>
<Label>Cimsswybclaeqjh</Label>
<Epoch>97</Epoch>
</MyTypeWithNamespaces>

您已经删除了名称空间。

一个合乎逻辑的问题是,在序列化过程中,您能够不经过显式重写而从任意类型中删除所有名称空间吗?答案是肯定的,如何做到这一点是在我的下一个回答。

这是两个答案中的第二个。

如果希望在序列化过程中任意地从文档中去除所有名称空间,可以通过实现自己的 XmlWriter 来实现。

最简单的方法是从 XmlTextWriter 派生并重写发出命名空间的 StartElement 方法。当发出任何元素(包括根元素)时,XmlSerializer 将调用 StartElement 方法。通过重写每个元素的名称空间,并用空字符串替换它,您已经从输出中删除了名称空间。

public class NoNamespaceXmlWriter : XmlTextWriter
{
//Provide as many contructors as you need
public NoNamespaceXmlWriter(System.IO.TextWriter output)
: base(output) { Formatting= System.Xml.Formatting.Indented;}


public override void WriteStartDocument () { }


public override void WriteStartElement(string prefix, string localName, string ns)
{
base.WriteStartElement("", localName, "");
}
}

假设这是这种类型:

// explicitly specify a namespace for this type,
// to be used during XML serialization.
[XmlRoot(Namespace="urn:Abracadabra")]
public class MyTypeWithNamespaces
{
// private fields backing the properties
private int _Epoch;
private string _Label;


// explicitly define a distinct namespace for this element
[XmlElement(Namespace="urn:Whoohoo")]
public string Label
{
set {  _Label= value; }
get { return _Label; }
}


// this property will be implicitly serialized to XML using the
// member name for the element name, and inheriting the namespace from
// the type.
public int Epoch
{
set {  _Epoch= value; }
get { return _Epoch; }
}
}

下面是在序列化过程中如何使用这样的东西:

        var o2= new MyTypeWithNamespaces { ..intializers.. };
var builder = new System.Text.StringBuilder();
using ( XmlWriter writer = new NoNamespaceXmlWriter(new System.IO.StringWriter(builder)))
{
s2.Serialize(writer, o2, ns2);
}
Console.WriteLine("{0}",builder.ToString());

不过 XmlTextWriter 有点坏了。根据 参考文件,它在写入时不检查以下内容:

  • 属性和元素名称中的字符无效。

  • 不适合指定编码的 Unicode 字符 字符不符合指定的 编码时,XmlTextWriter 不会 将 Unicode 字符转义为 字符实体

  • 重复的属性。

  • DOCTYPE 公共中的字符 标识符或系统标识符

XmlTextWriter 的这些问题从。NET 框架,而且它们将继续存在,作为向下兼容。如果您不关心这些问题,那么请务必使用 XmlTextWriter。但是大多数人希望更可靠一点。

为了实现这一点,在序列化过程中仍然抑制名称空间,而不是从 XmlTextWriter 派生,定义抽象 XmlWriter及其24个方法的具体实现。

这里有一个例子:

public class XmlWriterWrapper : XmlWriter
{
protected XmlWriter writer;


public XmlWriterWrapper(XmlWriter baseWriter)
{
this.Writer = baseWriter;
}


public override void Close()
{
this.writer.Close();
}


protected override void Dispose(bool disposing)
{
((IDisposable) this.writer).Dispose();
}


public override void Flush()
{
this.writer.Flush();
}


public override string LookupPrefix(string ns)
{
return this.writer.LookupPrefix(ns);
}


public override void WriteBase64(byte[] buffer, int index, int count)
{
this.writer.WriteBase64(buffer, index, count);
}


public override void WriteCData(string text)
{
this.writer.WriteCData(text);
}


public override void WriteCharEntity(char ch)
{
this.writer.WriteCharEntity(ch);
}


public override void WriteChars(char[] buffer, int index, int count)
{
this.writer.WriteChars(buffer, index, count);
}


public override void WriteComment(string text)
{
this.writer.WriteComment(text);
}


public override void WriteDocType(string name, string pubid, string sysid, string subset)
{
this.writer.WriteDocType(name, pubid, sysid, subset);
}


public override void WriteEndAttribute()
{
this.writer.WriteEndAttribute();
}


public override void WriteEndDocument()
{
this.writer.WriteEndDocument();
}


public override void WriteEndElement()
{
this.writer.WriteEndElement();
}


public override void WriteEntityRef(string name)
{
this.writer.WriteEntityRef(name);
}


public override void WriteFullEndElement()
{
this.writer.WriteFullEndElement();
}


public override void WriteProcessingInstruction(string name, string text)
{
this.writer.WriteProcessingInstruction(name, text);
}


public override void WriteRaw(string data)
{
this.writer.WriteRaw(data);
}


public override void WriteRaw(char[] buffer, int index, int count)
{
this.writer.WriteRaw(buffer, index, count);
}


public override void WriteStartAttribute(string prefix, string localName, string ns)
{
this.writer.WriteStartAttribute(prefix, localName, ns);
}


public override void WriteStartDocument()
{
this.writer.WriteStartDocument();
}


public override void WriteStartDocument(bool standalone)
{
this.writer.WriteStartDocument(standalone);
}


public override void WriteStartElement(string prefix, string localName, string ns)
{
this.writer.WriteStartElement(prefix, localName, ns);
}


public override void WriteString(string text)
{
this.writer.WriteString(text);
}


public override void WriteSurrogateCharEntity(char lowChar, char highChar)
{
this.writer.WriteSurrogateCharEntity(lowChar, highChar);
}


public override void WriteValue(bool value)
{
this.writer.WriteValue(value);
}


public override void WriteValue(DateTime value)
{
this.writer.WriteValue(value);
}


public override void WriteValue(decimal value)
{
this.writer.WriteValue(value);
}


public override void WriteValue(double value)
{
this.writer.WriteValue(value);
}


public override void WriteValue(int value)
{
this.writer.WriteValue(value);
}


public override void WriteValue(long value)
{
this.writer.WriteValue(value);
}


public override void WriteValue(object value)
{
this.writer.WriteValue(value);
}


public override void WriteValue(float value)
{
this.writer.WriteValue(value);
}


public override void WriteValue(string value)
{
this.writer.WriteValue(value);
}


public override void WriteWhitespace(string ws)
{
this.writer.WriteWhitespace(ws);
}




public override XmlWriterSettings Settings
{
get
{
return this.writer.Settings;
}
}


protected XmlWriter Writer
{
get
{
return this.writer;
}
set
{
this.writer = value;
}
}


public override System.Xml.WriteState WriteState
{
get
{
return this.writer.WriteState;
}
}


public override string XmlLang
{
get
{
return this.writer.XmlLang;
}
}


public override System.Xml.XmlSpace XmlSpace
{
get
{
return this.writer.XmlSpace;
}
}
}

然后,提供一个重写 StartElement 方法的派生类,如前所述:

public class NamespaceSupressingXmlWriter : XmlWriterWrapper
{
//Provide as many contructors as you need
public NamespaceSupressingXmlWriter(System.IO.TextWriter output)
: base(XmlWriter.Create(output)) { }


public NamespaceSupressingXmlWriter(XmlWriter output)
: base(XmlWriter.Create(output)) { }


public override void WriteStartElement(string prefix, string localName, string ns)
{
base.WriteStartElement("", localName, "");
}
}

然后像这样使用这个作者:

        var o2= new MyTypeWithNamespaces { ..intializers.. };
var builder = new System.Text.StringBuilder();
var settings = new XmlWriterSettings { OmitXmlDeclaration = true, Indent= true };
using ( XmlWriter innerWriter = XmlWriter.Create(builder, settings))
using ( XmlWriter writer = new NamespaceSupressingXmlWriter(innerWriter))
{
s2.Serialize(writer, o2, ns2);
}
Console.WriteLine("{0}",builder.ToString());

这要归功于 Oleg Tkachenko

...
XmlSerializer s = new XmlSerializer(objectToSerialize.GetType());
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("","");
s.Serialize(xmlWriter, objectToSerialize, ns);

在网上阅读了微软的文档和一些解决方案之后,我发现了这个问题的解决方案。它可以通过 IXmlSerialiazble使用内置的 XmlSerializer和自定义的 XML 序列化。

也就是说,我将使用到目前为止在这个问题的答案中使用过的相同 MyTypeWithNamespaces XML 示例。

[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;
}

这节课就到这里。现在,一些人反对在他们的类中的某个地方使用 XmlSerializerNamespaces对象,但是正如你所看到的,我把它巧妙地藏在了缺省构造函数中,并公开了一个公共属性来返回名称空间。

现在,当需要序列化该类时,您将使用以下代码:

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");


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


// 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);

完成此操作后,应该会得到以下输出:

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

我在最近的一个项目中成功地使用了这种方法,该项目具有深层次的类,这些类被序列化为 XML 用于 Web 服务调用。微软的文档并不十分清楚,一旦创建了公开可访问的 XmlSerializerNamespaces成员,该如何处理它,因此许多人认为它是无用的。但是通过遵循他们的文档并按照上面所示的方式使用它,您可以自定义 XmlSerializer 如何为您的类生成 XML,而不必采用不受支持的行为或通过实现 IXmlSerializable“滚动您自己的”序列化。

我希望这个答案能够彻底解决如何摆脱由 XmlSerializer生成的标准 xsixsd名称空间的问题。

更新: 我只是想确保我回答了 OP 关于删除所有名称空间的问题。我上面的代码将为此工作; 让我向您展示如何工作。现在,在上面的示例中,您实际上不能去掉所有的名称空间(因为有两个正在使用的名称空间)。在 XML 文档的某个地方,您需要使用类似 xmlns="urn:Abracadabra" xmlns:w="urn:Whoohoo的东西。如果示例中的类是较大文档的一部分,那么必须为 AbracadbraWhoohoo中的一个(或两个)声明名称空间之上的某个位置。如果没有,那么其中一个或两个名称空间中的元素必须使用某种类型的前缀进行装饰(不能有两个默认名称空间,对吗?).因此,对于这个示例,Abracadabra是默认名称空间。我可以在我的 MyTypeWithNamespaces类中为 Whoohoo名称空间添加一个名称空间前缀,如下所示:

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

现在,在我的类定义中,我指出 <Label/>元素位于名称空间 "urn:Whoohoo"中,因此我不需要做任何进一步的工作。当我现在使用上述序列化代码序列化该类时,输出结果如下:

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

因为 <Label>与文档的其余部分位于不同的名称空间中,所以它必须以某种方式用名称空间“装饰”。注意,仍然没有 xsixsd名称空间。

XmlSerializer sr = new XmlSerializer(objectToSerialize.GetType());
TextWriter xmlWriter = new StreamWriter(filename);
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
sr.Serialize(xmlWriter, objectToSerialize, namespaces);