【问题标题】:Is there a way to remove *only* the xmlns:xsd namespace when serializing a XML?有没有办法在序列化 XML 时删除 *only* xmlns:xsd 命名空间?
【发布时间】:2018-01-23 01:39:59
【问题描述】:

我使用 xsd2code++ 创建了一个序列化对象。它工作正常,除了以下内容:

出于某种原因,我需要发送的 XML 需要有一些命名空间,但不是 xsd 的(即使,据我了解,列出它应该没有问题,但这不是我的要求)。我在生成的 c# 类中有以下代码:

[XmlNamespaceDeclarations]
public XmlSerializerNamespaces xmlns;

[XmlAttribute("schemaLocation", Namespace = XmlSchema.InstanceNamespace)]
public string xsiSchemaLocation = "http://www.sat.gob.mx/TimbreFiscalDigital http://www.sat.gob.mx/sitio_internet/cfd/TimbreFiscalDigital/TimbreFiscalDigitalv11.xsd";
public TimbreFiscalDigital()
{
  this.versionField = "1.1";
  this.xmlns = new XmlSerializerNamespaces();
  this.xmlns.Add("tfd", "http://www.sat.gob.mx/TimbreFiscalDigital");
  this.xmlns.Add("xsi", "http://www.w3.org/2001/XMLSchema-instance");
}

在类本身的属性中,我有以下内容:

[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.sat.gob.mx/TimbreFiscalDigital")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://www.sat.gob.mx/TimbreFiscalDigital", IsNullable = false)]

但 XML 包含 xmlns:xsd="http://www.w3.org/2001/XMLSchema" 命名空间声明。

我在这里看到了关于如何完全删除用于序列化的命名空间的不同答案,但这不是我想要的,我只是希望我设置的那些是唯一序列化的。

有什么方法可以删除 xsd,而无需使用自定义 xml 序列化程序或类似的东西?我觉得可能有一些我可以设置的属性或选项(或者我设置不正确)会影响到这一点,但我在对象的代码中没有看到对xsd 的任何引用。我已经通过XmlSerializer.Serialize() 直接调用序列化程序了。

【问题讨论】:

  • 1) 您是直接调用序列化程序,还是被某些框架间接调用? 2) 我在这里看到了关于如何完全删除用于序列化的命名空间的不同答案 - 哪些答案?如果您手动进行序列化,您可以使用 this answer 删除 xsdxsi 命名空间 - 然后手动添加 xsi 命名空间。
  • @dbc 1) 我直接调用序列化程序。 2)这是我看到的问题/答案之一。对于我所看到的,我已经在这样做了:我正在创建 XmlSerializerNamespaces 并添加我想要的那些,但 xsd 仍然不断出现。我将尝试先添加 Empty 一个,然后添加我需要的 2 个,看看它是否会改变事情。 - 我检查了代码并且对Serialize 的调用没有包含最后一个参数。我不确定我添加的命名空间是如何添加的,但我正在尝试添加该参数,看看是否只有我需要的 2 个出现。
  • 我无法重现该问题,请参阅dotnetfiddle.net/hZksl8。然而,我能够看到 "http://www.sat.gob.mx/TimbreFiscalDigital" 命名空间出现在两个单独的命名空间属性中。我可以通过将XmlSerializerNamespaces 直接传递给XmlSerializer.Serialize() 来解决这个问题。你能提供一个minimal reproducible example 来演示插入xsd 命名空间的情况吗?
  • @dbc 是的,将 xmlns 实例传递给 XmlSerializer.Serialize() 可以解决问题。奇怪的是,显然您不需要传递该参数以显示使用的命名空间(tfd 和 xsi),但只有它们出现,您需要传递参数。
  • 这在最后一个 remark for XmlNamespaceDeclarationsAttribute 中的记录模糊且记录不佳:还请注意,应用该属性的成员仅包含属于定义的 XML 元素的前缀-命名空间对 这基本上意味着 子元素 使用前缀而不是当前元素 - 所以它并不能真正按预期或根元素想要的那样工作。

标签: c# xml xml-serialization xmlserializer


【解决方案1】:

XmlNamespaceDeclarationsAttribute 不影响根元素的命名空间前缀的原因在其documentation 中进行了解释,尽管不清楚:

还要注意,应用该属性的成员仅包含 属于 类定义的 XML 元素的前缀-命名空间对。例如,在以下 XML 文档中,仅捕获前缀对“cal”,而不捕获前缀“x”。要获取该数据,请将具有 XmlNamespaceDeclarationsAttribute 的成员添加到表示 root 元素的类。

<?xml version="1.0"?>
<x:root xmlns:x="http://www.cohowinery.com/x/">
  <x:select xmlns:cal="http://www.cohowinery.com/calendar/" path="cal:appointments/@cal:startTime" />
</x:root>

这意味着返回的前缀-命名空间对将被添加到当前元素的属性中,并在序列化子元素(那些属于当前元素的元素)时使用——但会不影响当前元素本身的命名空间前缀。为此,必须将XmlNamespaceDeclarationsAttribute 成员添加到父级——当然,根元素没有父级。

在没有控制根元素的命名空间前缀的属性的情况下,必须使用XmlSerializer.Serialize(Writer, Object, XmlSerializerNamespaces) 重载之一手动调用XmlSerializer。如果您使用不包含 XmlSerializerNamespacesSerialize() 重载,例如 XmlSerializer.Serialize(XmlWriter, Object),则序列化程序将始终有助于将一组默认命名空间添加到根元素,包括:

  • 根元素本身所需的命名空间;
  • 标准的“xsd”和“xsi”命名空间;
  • 子节点需要额外的命名空间;
  • XmlNamespaceDeclarations 成员返回的其他命名空间。

这是您看到的行为。

因此,以下扩展方法将根据需要序列化您的根对象:

public static partial class XmlSerializationHelper
{
    public static string GetXml<T>(this T obj, XmlSerializer serializer = null, XmlSerializerNamespaces ns = null)
    {
        ns = ns ?? obj.GetXmlNamespaceDeclarations();
        using (var textWriter = new StringWriter())
        {
            var settings = new XmlWriterSettings() { Indent = true }; // For cosmetic purposes.
            using (var xmlWriter = XmlWriter.Create(textWriter, settings))
                (serializer ?? new XmlSerializer(obj.GetType())).Serialize(xmlWriter, obj, ns);
            return textWriter.ToString();
        }
    }

    public static XmlSerializerNamespaces GetXmlNamespaceDeclarations<T>(this T obj)
    {
        if (obj == null)
            return null;
        var type = obj.GetType();
        return type.GetFields()
            .Where(f => Attribute.IsDefined(f, typeof(XmlNamespaceDeclarationsAttribute)))
            .Select(f => f.GetValue(obj))
            .Concat(type.GetProperties()
                .Where(p => Attribute.IsDefined(p, typeof(XmlNamespaceDeclarationsAttribute)))
                .Select(p => p.GetValue(obj, null)))
            .OfType<XmlSerializerNamespaces>()
            .SingleOrDefault();
    }

    public static XmlSerializerNamespaces With(this XmlSerializerNamespaces xmlns, string prefix, string ns)
    {
        if (xmlns == null)
            throw new ArgumentNullException();
        xmlns.Add(prefix, ns);
        return xmlns;
    }       
}

那么如果你按如下方式序列化你的类型:

var root = new TimbreFiscalDigital();
var xml = root.GetXml();

生成以下 XML:

<tfd:TimbreFiscalDigital xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sat.gob.mx/TimbreFiscalDigital http://www.sat.gob.mx/sitio_internet/cfd/TimbreFiscalDigital/TimbreFiscalDigitalv11.xsd" xmlns:tfd="http://www.sat.gob.mx/TimbreFiscalDigital">
  <tfd:version>1.1</tfd:version>
</tfd:TimbreFiscalDigital>

示例fiddle

顺便说一句,如果TimbreFiscalDigital.xmlns 返回的命名空间是固定的,并且您不需要在反序列化期间捕获它们,则可以将该字段替换为应用了[XmlNamespaceDeclarations] 的属性,如下所示:

[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.sat.gob.mx/TimbreFiscalDigital")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://www.sat.gob.mx/TimbreFiscalDigital", IsNullable = false)]
public class TimbreFiscalDigital
{
    string versionField;

    //[XmlAttribute]
    public string version { get { return versionField; } set { versionField = value; } }

    [XmlNamespaceDeclarations]
    public XmlSerializerNamespaces Xmlns 
    {
        get
        {
            return new XmlSerializerNamespaces()
                .With("tfd", "http://www.sat.gob.mx/TimbreFiscalDigital")
                .With("xsi", XmlSchema.InstanceNamespace);
        }
        set { /* Do nothing */ }
    }

    [XmlAttribute("schemaLocation", Namespace = XmlSchema.InstanceNamespace)]
    public string xsiSchemaLocation = "http://www.sat.gob.mx/TimbreFiscalDigital http://www.sat.gob.mx/sitio_internet/cfd/TimbreFiscalDigital/TimbreFiscalDigitalv11.xsd";

    public TimbreFiscalDigital()
    {
        this.versionField = "1.1";
    }
}

该属性必须同时具有 getter 和 setter,但当 getter 始终返回 XmlSerializerNamespaces 的新实例时,setter 无能为力。通过这样做,您可以减少类的永久内存占用。

示例fiddle #2

【讨论】:

  • 感谢您的回答和 cmets,使用包含命名空间的 Serialize 的重载解决了这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-13
相关资源
最近更新 更多