XmlSerializer:删除不必要的xsi和xsd命名空间


Answers:


63

由于Dave要求我在序列化.NET中的对象时重复我的回答,即省略所有xsi和xsd命名空间,因此,我更新了这篇文章,并从上述链接在这里重复了我的回答。此答案中使用的示例与其他问题使用的示例相同。逐字复制以下内容。


在在线阅读Microsoft的文档和几种解决方案后,我发现了解决此问题的方法。它可以通过内置XmlSerializer和自定义XML序列化一起使用IXmlSerialiazble

到目前为止,我将使用与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创建公开可访问成员后,Microsoft的文档尚不清楚如何处理它,因此许多人认为它没有用。但是通过遵循他们的文档并以上面显示的方式使用它,您可以自定义XmlSerializer如何为您的类生成XML,而无需诉诸不受支持的行为或通过实现实现“滚动自己的”序列化IXmlSerializable

我希望这个答案能够一劳永逸地解决如何摆脱产生的标准xsixsd名称空间XmlSerializer

更新:我只想确保我回答了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名称空间。


这结束了我对另一个问题的回答。但是我想确保我回答了OP关于不使用名称空间的问题,因为我觉得我还没有真正解决它。假设<Label>与文档的其余部分属于同一名称空间,在这种情况下urn:Abracadabra

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

您的构造函数将与我的第一个代码示例中的样子相同,并带有public属性以检索默认名称空间:

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

然后,稍后,在使用该MyTypeWithNamespaces对象序列化该对象的代码中,您将像上面一样调用它:

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

然后,XmlSerializer将吐出上面刚刚显示的相同XML,而输出中没有其他名称空间:

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

为了完整起见,也许您应该在此处提供正确的答案,而不是简单地提及它,而且,我很想知道您如何得出结论,认为这是“不被支持的行为”。
Dave Van den Eynde 2012年

1
再次来到这里进行检查,因为这是我找到的最直接的解释。感谢@fourpastmidnight
安德烈·阿尔伯克基

2
我不明白,对于您的最终OP的答案,您在序列化过程“ urn:Abracadabra”(构造函数)中仍在使用名称空间,为什么最终输出中不包括该名称空间。OP不应该使用:XmlSerializerNamespaces EmptyXmlSerializerNamespaces = new XmlSerializerNamespaces(new [] {XmlQualifiedName.Empty});
dparkar,2013年

2
这是正确的答案,尽管它不是投票最多的。对我不起作用的麻烦的事情是XmlTextWriter xtw = (XmlTextWriter)XmlTextWriter.Create(ms, xws);我必须替换为var xtw = XmlTextWriter.Create(memStm, xws);
Leonel Sanches da Silva

1
自从我写了这个答案已经有一段时间了。XmlTextWriter.Create返回一个(抽象的?)XmlWriter实例。因此,@ Preza8是正确的,您将失去设置其他XmlTextWriter特定属性的能力(至少在没有向下转换的情况下),因此将特定的类型转换为XmlTextWriter
fourpastmidnight

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

24
嗯...你们是叛军。它在msdn.microsoft.com/zh-cn/library/上明确表示您无法做到这一点。
拉尔夫·拉维尔

布尔耶!(因为克服了女士说的你不能做的事情)
granadaCoder 2013年

3
我不确定为什么它是“不受支持的”,但这确实符合我的要求。
丹·贝查德

8
此答案生成“ xmlns:d1p1”和“ xmlns:q1”名称空间。这是什么?
Leonel Sanches da Silva

2
好的,此代码适用于非常简单的序列化,而无需其他名称空间定义。对于多个名称空间定义,有效的答案是公认的答案。
Leonel Sanches da Silva

6

还有一种选择-您可以提供要序列化的XmlSerializerNamespaces类型的成员。用XmlNamespaceDeclarations属性装饰它。将名称空间前缀和URI添加到该成员。然后,任何未显式提供XmlSerializerNamespaces的序列化将使用您在类型中输入的名称空间前缀+ URI对。

示例代码,假设这是您的类型:

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

你可以这样做:

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

这将意味着该实例的任何未指定其自己的前缀+ URI对集合的序列化都将对“ urn:mycompany.2009”名称空间使用“ p”前缀。它还将省略xsi和xsd名称空间。

此处的区别在于,您是将XmlSerializerNamespaces添加到类型本身,而不是在对XmlSerializer.Serialize()的调用中显式使用它。这意味着,如果您的类型的实例是由您不拥有的代码序列化的(例如,在webservices堆栈中),并且该代码未显式提供XmlSerializerNamespaces,则该序列化程序将使用实例中提供的名称空间。


1.我看不出有什么区别。您仍在向XmlSerializerNamespaces实例添加默认名称空间。
戴夫·范·登·爱因德

3
2.这会更加污染课堂。我的目标是不使用某些命名空间,我的目标是根本不使用命名空间。
戴夫·范·登·爱因德

我添加了关于此方法与仅在序列化期间指定XmlSerializerNamespaces之间的区别的注释。
Cheeso

0

我正在使用:

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());
            }
        }
    }
}

要获取以下XML:

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

如果您不想要名称空间,只需将DEFAULT_NAMESPACE设置为“”即可。


尽管这个问题已有10多年的历史了,但那时的重点是要拥有一个根本不包含任何名称空间声明的XML正文。
Dave Van den Eynde

1
如果我对10年之久的问题添加自己的答案,那是因为被接受的答案比完整版圣经的阅读时间更长。
Maxence

投票最多的答案是建议不要使用的方法(空名称空间)。
Maxence

我没办法 我只能做出我认为最正确的答案。
Dave Van den Eynde
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.