移除序列化器创建的空xmlns

4

我拥有一个通过“添加服务引用…”操作生成的对象,我正在使用自己编写的通用序列化程序手动对其进行序列化。

我的问题是数据合同中包含一些内部对象。

序列化程序会在内部对象的起始标签上添加一个空命名空间属性。是否有办法阻止它发生?


5
为什么会有问题?如果元素在默认命名空间中,那么 xmlns="" 是正确的。 - John Saunders
xmlns="" 可能是正确的,但我无法控制接收它的WS,并且它与它们不兼容。有没有办法去除它们? - Elad Lachmi
如果xmlns=""破坏了你的XML代码读取,那么它就出现了非常严重的错误和bug,需要修复。它无法读取一些基本的、正确的XML。务必做一个好邻居,告诉那段代码的所有者应该修复它以处理默认命名空间。 - John Saunders
你说的“我写的通用序列化器”是什么意思?如果它是自定义序列化器,那么它的行为不是由你自己定义的吗? - Buh Buh
3个回答

8
如果将您的内部对象归属于与根相同的命名空间,那么从子级中省略xmlns声明就是正确的方式。您可以使用[assembly: ContractNamespace]属性来覆盖程序集中所有合同的命名空间。请参考数据合同名称中的示例。

编辑:以下是一些示例的详细说明。

假设您正在手动构建一个XML文档,并且没有为任何元素指定命名空间。

XDocument xmlDocument = new XDocument(
    new XElement("Book",
        new XElement("Title", "Animal Farm"),
        new XElement("Author", "George Orwell"),
        new XElement("Publisher",
            new XElement("Name", "Secker and Warburg"),
            new XElement("Location", "London"),
            new XElement("Founded", 1910))));
return xmlDocument.ToString();

生成的 XML 不包含命名空间声明,这与预期一致。
<Book>
  <Title>Animal Farm</Title>
  <Author>George Orwell</Author>
  <Publisher>
    <Name>Secker and Warburg</Name>
    <Location>London</Location>
    <Founded>1910</Founded>
  </Publisher>
</Book>

如果您为根元素指定了一个命名空间,那么所有子元素必须明确地退出该默认命名空间,使用xml = ""声明。 根据命名空间默认规则

默认命名空间声明的范围从其出现在的开始标记到相应的结束标记的末尾,不包括任何内部默认命名空间声明的范围。 对于空标签,范围是标记本身。

因此,针对根元素指定命名空间的以下代码...

XDocument xmlDocument = new XDocument(
    new XElement("{http://example.com/library}Book",
        new XElement("Title", "Animal Farm"),
        new XElement("Author", "George Orwell"),
        new XElement("Publisher",
            new XElement("Name", "Secker and Warburg"),
            new XElement("Location", "London"),
            new XElement("Founded", 1910))));
return xmlDocument.ToString();

...将生成以下XML:

<Book xmlns="http://example.com/library">
  <Title xmlns="">Animal Farm</Title>
  <Author xmlns="">George Orwell</Author>
  <Publisher xmlns="">
    <Name>Secker and Warburg</Name>
    <Location>London</Location>
    <Founded>1910</Founded>
  </Publisher>
</Book>

请注意,<Publisher>元素的子元素不需要退出根命名空间,因为它们从其父级继承“无命名空间”声明。
为了消除xmlns=""声明,在演示的目的下,我们可以将相同的命名空间分配给所有后代。
XDocument xmlDocument = new XDocument(
    new XElement("{http://example.com/library}Book",
        new XElement("{http://example.com/library}Title", "Animal Farm"),
        new XElement("{http://example.com/library}Author", "George Orwell"),
        new XElement("{http://example.com/library}Publisher",
            new XElement("{http://example.com/library}Name", "Secker and Warburg"),
            new XElement("{http://example.com/library}Location", "London"),
            new XElement("{http://example.com/library}Founded", 1910))));
return xmlDocument.ToString();

这将生成一个只在根元素中声明命名空间(并在所有后代中隐式继承)的XML文档:
<Book xmlns="http://example.com/library">
  <Title>Animal Farm</Title>
  <Author>George Orwell</Author>
  <Publisher>
    <Name>Secker and Warburg</Name>
    <Location>London</Location>
    <Founded>1910</Founded>
  </Publisher>
</Book>

为了模拟您涉及Web服务的情况,我们可以创建以下WCF服务。
[DataContract]
public class Book
{
    [DataMember]
    public string Title { get; set; }
    [DataMember]
    public string Author { get; set; }
    [DataMember]
    public Publisher Publisher { get; set; }
}

[DataContract]
public class Publisher
{
    [DataMember]
    public string Name { get; set; }
    [DataMember]
    public string Location { get; set; }
    [DataMember]
    public short Founded { get; set; }
}

[ServiceContract]
public interface ILibraryService
{
    [OperationContract]
    Book GetBook();
}

public class LibraryService : ILibraryService
{
    public Book GetBook()
    {
        return new Book
        {
            Title = "Animal Farm",
            Author = "George Orwell",
            Publisher = new Publisher
            {
                Name = "Secker and Warburg",
                Location = "London",
                Founded = 1910,
            }
        };
    }
}

我们在客户端应用中添加了一个服务引用,调用其操作并将结果序列化,同时将其封装在一个具有显式命名空间的根Books元素中:
using (var libraryClient = new LibraryServiceReference.LibraryServiceClient())
{
    var book = libraryClient.GetBook();

    var stringBuilder = new StringBuilder();
    using (XmlWriter xmlWriter = XmlWriter.Create(stringBuilder))
    {
        xmlWriter.WriteStartElement("Books", "http://example.com/library");
        var serializer = new XmlSerializer(book.GetType());
        serializer.Serialize(xmlWriter, book);
        xmlWriter.WriteEndElement();
    }

    return stringBuilder.ToString();
}

在这种情况下,内部元素Book包含一个xmlns=""声明。
<?xml version="1.0" encoding="utf-16"?>
<Books xmlns="http://example.com/library">
  <Book xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
        xmlns="">
    <ExtensionData />
    <Author>George Orwell</Author>
    <Publisher>
      <ExtensionData />
      <Founded>1910</Founded>
      <Location>London</Location>
      <Name>Secker and Warburg</Name>
    </Publisher>
    <Title>Animal Farm</Title>
  </Book>
</Books>

如上所述,可以通过将Book元素(以及其后代)的命名空间设置为与根元素相对应来消除xmlns=""。对于XmlSerializer类,所有元素的默认命名空间可以通过其构造函数的第二个参数指定。(实际技术会因使用的序列化策略而异。)

using (var libraryClient = new LibraryServiceReference.LibraryServiceClient())
{
    var book = libraryClient.GetBook();

    var stringBuilder = new StringBuilder();
    using (XmlWriter xmlWriter = XmlWriter.Create(stringBuilder))
    {
        xmlWriter.WriteStartElement("Books", "http://example.com/library");
        var serializer = new XmlSerializer(book.GetType(), "http://example.com/library");
        serializer.Serialize(xmlWriter, book);
        xmlWriter.WriteEndElement();
    }

    return stringBuilder.ToString();
}

这将会得到预期的结果:

<?xml version="1.0" encoding="utf-16"?>
<Books xmlns="http://example.com/library">
  <Book xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
        xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <ExtensionData />
    <Author>George Orwell</Author>
    <Publisher>
      <ExtensionData />
      <Founded>1910</Founded>
      <Location>London</Location>
      <Name>Secker and Warburg</Name>
    </Publisher>
    <Title>Animal Farm</Title>
  </Book>
</Books>

我最终使用了数据契约序列化器而不是XmlSerializer,这使得它可以直接使用,但非常感谢您的答案。赏金已获得。 - Elad Lachmi
1
谢谢! :-) 我本来想提到 DataContractSerializer,但我(错误地)假设服务本身为其数据契约声明了空的(或不正确的)命名空间(例如 [DataContract(Namespace="")]),这在客户端使用该序列化程序绕过是不容易的。XmlSerializer 忽略在数据契约上声明的命名空间,允许您替换自己的命名空间。无论如何,很高兴您找到了一个简单的解决方案! - Douglas

3
这可能有点离题,如果您使用的是 [DataContract],则可能不适用。而这适用于从 WSDL 生成的代理代码(在与 Java 终结点的互操作环境中,我们被告知 xmlns="" 是无效的)。因此,我在这里提供这个信息以帮助您。
XmlElementAttribute.Form 属性设置为 System.Xml.Schema.XmlSchemaForm.Unqualified 时,WCF 请求中的子成员可能会输出 xmlns=""。
[System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified)]
public string MyProperty {
     get; set;
}

这将产生类似以下内容的结果。
<MyObject xmlns="http://some.namespance">
   <MyProperty xmlns="">My value goes here</MyProperty>
</MyObject>

将其设置为System.Xml.Schema.XmlSchemaForm.None(默认值)意味着它不会输出“未限定的”命名空间属性。
[System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.None)]
public string MyProperty {
    get; set;
}

这会生成以下内容:

<MyObject xmlns="http://some.namespance">
   <MyProperty>My value goes here</MyProperty>
</MyObject>

我不确定在导入wsdl引用时是否可以更改此行为,或者可能应该更改wsdl,但最终我直接编辑了生成的代理代码(这绝对不是理想的方法),但实现了我的即时目标。


1
如果您可以控制序列化器,您可以始终添加一个空的命名空间以确保在输出XML中省略xmlns。例如:
var serializer = new XmlSerializer(target.GetType()); 
var ns = new XmlSerializerNamespaces(); 
ns.Add("",""); 
serializer.Serialize(xmlWriter, target, ns); 

此致敬礼,


1
我尝试过那样做,但它会从所有元素中删除xmlns。我需要根仍然具有xmlns(不为空)。 - Elad Lachmi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接