【问题标题】:How to serialize XML with explicitly defined namespaces that matches inherited ancestor's namespace?如何使用与继承的祖先命名空间匹配的明确定义的命名空间序列化 XML?
【发布时间】:2016-07-14 19:02:30
【问题描述】:

TLDR 版本

我将对象序列化为 XML 以匹配第三方提供的模式。他们的验证器要求其中一个子对象具有显式声明的名称空间,该名称空间与其祖先的名称空间相匹配。数据足够复杂,我不想为此目的推出自己的序列化程序。如何强制 XMLSerializer 类显式呈现命名空间,即使它在技术上是多余的?

完整版

我遇到了 XMLSerializer 未呈现 CoreItemsMkt 命名空间的问题。我相信这是因为属性和命名空间都与它所继承的祖先命名空间完全匹配,因此序列化程序会忽略它 - 但是,提交此文件的站点验证器需要它。

例如:

<?xml version="1.0" encoding="utf-8"?>
<FSAMarketsFeed xmlns="http://www.fsa.gov.uk/XMLSchema/FSAMarketsFeed-v1-2">
 <FSAFeedHeader xmlns="http://www.fsa.gov.uk/XMLSchema/FSAFeedCommon-v1-2">
  [...contents omitted, this item appears once...]
 </FSAFeedHeader>
 <FSAMarketsFeedMsg>
   <CoreItemsMkt xmlns="http://www.fsa.gov.uk/XMLSchema/FSAMarketsFeed-v1-2"> <!--//This namespace is the issue//-->
    [...contents omitted, this item appears multiple times...]
   </CoreItemsMkt?
 </FSAMarketsFeedMsg>
 <FSAMarketsFeedMsg>
   <CoreItemsMkt xmlns="http://www.fsa.gov.uk/XMLSchema/FSAMarketsFeed-v1-2"> <!--//This namespace is the issue//-->
    [...contents omitted, this item appears multiple times...]
   </CoreItemsMkt?
 </FSAMarketsFeedMsg>

我正在使用这样的方法进行序列化:

        var path = GetFilePath();

        var ns = new XmlSerializerNamespaces();
        ns.Add("", "http://www.fsa.gov.uk/XMLSchema/FSAMarketsFeed-v1-2");

        var ser = new XmlSerializer(typeof(FSAMarketsFeed));
        var settings = new XmlWriterSettings
        { Encoding = Encoding.UTF8, Indent = true, IndentChars = "\t", NamespaceHandling = NamespaceHandling.Default };
        using (var writer = XmlWriter.Create(path, settings))
        {
            ser.Serialize(writer, GetDataToSerialize(), ns);
        }

我的根类定义为:

[XmlType(AnonymousType = true)]
[XmlRoot(Namespace = "http://www.fsa.gov.uk/XMLSchema/FSAMarketsFeed-v1-2", IsNullable = false)]
public class FSAMarketsFeed
{
    public FSAMarketsFeed()
    {
        FSAMarketsFeedMsg = new FSAMarketsFeedMsg[0];
    }

    [XmlElement("FSAFeedHeader", IsNullable = true, Namespace = "http://www.fsa.gov.uk/XMLSchema/FSAFeedCommon-v1-2")]
    public FSAFeedHeader FeedHeader { get; set; }

    [XmlElement("FSAMarketsFeedMsg")]
    public FSAMarketsFeedMsg[] FSAMarketsFeedMsg { get; set; }
}

工作提要标题类:

[XmlType(AnonymousType = true)]
public class FSAFeedHeader
{
    [XmlElement("FeedTargetSchemaVersion", IsNullable = true)]
    public string FeedTargetSchemaVersion { get; set; }

    [XmlElement("Submitter", IsNullable = true)]
    public Submitter Submit { get; set; }

    [XmlElement("ReportDetails", IsNullable = true)]
    public ReportDetails ReportDetail { get; set; }
}

父 Feed 消息类:

[XmlType(AnonymousType = true)]
public class FSAMarketsFeedMsg
{
    [XmlElement("CoreItemsMkt", IsNullable = true, Namespace = "http://www.fsa.gov.uk/XMLSchema/FSAMarketsFeed-v1-2")]
    public CoreItemsMkt CoreMarket { get; set; }

    [XmlElement("Transaction", IsNullable = true)]
    public Transaction Trans { get; set; }
}

最后,未能呈现其命名空间的 CoreItemsMkt 类:

[XmlType(Namespace = "http://www.fsa.gov.uk/XMLSchema/FSAMarketsFeed-v1-2", AnonymousType = true)]
public class CoreItemsMkt
{
    //[... Children omitted ...]]
}

到目前为止尝试过:

  • 使用 XMmlType(AnonymousType = true) 尝试打破继承链
  • 将 xmlns 显式设置为带有硬编码值的 XmlAttributeAttribute。
  • 在 CoreItemsMkt 上设置和删除 XmlType(Namespace = "http://www.fsa.gov.uk/XMLSchema/FSAMarketsFeed-v1-2")
  • 在 FSAMArketsFeedMsg 的属性上添加和删除 XmlElement(Namespace = "the value")。
  • 在 CoreItmsMkt 上实现 ISerializable(虽然不太清楚如何让它工作。)
  • 堆栈溢出搜索 - 我发现 1 个类似问题的答案是“不支持,请更改您的输出命名空间”。不幸的是,这个答案对我不起作用。

那么,在不手动渲染的情况下,有没有办法强制 XmlSerializer 类在 CoreItmsMkt 上渲染这些命名空间属性?

【问题讨论】:

  • 只是为了确认,通过明确声明的模式与它的祖先的模式匹配,您的意思是明确声明的命名空间与它的祖先的命名空间?
  • @dbc... 是正确的。对不起,在这个和一个 SQL 项目之间来回走动。稍后将进行编辑以更正。

标签: c# xml serialization xml-namespaces xmlserializer


【解决方案1】:

尝试使用自定义 XML 编写器。

public class CustomWriter : XmlTextWriter
{
    public CustomWriter(TextWriter writer) : base(writer) { }
    public CustomWriter(Stream stream, Encoding encoding) : base(stream, encoding) { }
    public CustomWriter(string filename, Encoding encoding) : base(filename, encoding) { }

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

        if (localName == "CoreItemsMkt")
        {
            base.WriteAttributeString("xmlns",
                "http://www.fsa.gov.uk/XMLSchema/FSAMarketsFeed-v1-2");
            //base.WriteAttributeString("xmlns", ns);
        }
    }
}

自定义编写器将 required 属性强制添加到具有 CoreItemsMkt 名称的每个元素。

用法

using (var customWriter = new CustomWriter(path, Encoding.UTF8))
{
    customWriter.Formatting = Formatting.Indented;
    customWriter.Indentation = 1;
    customWriter.IndentChar = '\t';

    ser.Serialize(customWriter, GetDataToSerialize(), ns);
}

【讨论】:

  • 虽然我可以看到当需要更多可定制性时这会变得很尴尬,但这个答案巧妙地解决了我的问题。谢谢。
【解决方案2】:

您希望能够在序列化指定的嵌套元素时强制XmlSerializer 发出冗余的xmlns= 属性。不幸的是,我不知道有任何 API 可以自动实现这一点。您还写了数据足够复杂,我不想为此目的推出我自己的序列化程序,因此您不想在FSAMarketsFeedMsg 上实现IXmlSerializable。 (ISerializable 不被XmlSerializer 使用,所以实现它不会有帮助。)因此你会想要做一些“半手动”的事情。至少有两种选择。

选项 1:序列化为临时的 XDocument,然后修复属性。

使用此解决方案,您可以序列化到内存中的一个临时XDocument,然后为每个所需的冗余xmlns= 添加一个XAttribute,如下所示:

// Generate the temporary XDocument
var ns = Namespaces.GetFSAMarketsFeedNamespace();
var doc = data.SerializeToXDocument(null, ns);
var root = doc.Root;

// Add redundate xmlns= attributes
var name = XName.Get("CoreItemsMkt", Namespaces.FSAMarketsFeed);
var query = doc.Descendants(name); // Could be a more complex query, possibly even an XPath query.

foreach (var element in query)
{
    if (!element.Attributes().Any(a => a.IsNamespaceDeclaration))
    {
        var prefix = element.GetPrefixOfNamespace(element.Name.Namespace);
        if (string.IsNullOrEmpty(prefix))
            element.Add(new XAttribute("xmlns", element.Name.NamespaceName));
        else
            element.Add(new XAttribute(XNamespace.Xmlns + prefix, element.Name.NamespaceName));
    }
}

// Write the XDocument to disk.

使用静态扩展类:

public static class Namespaces
{
    public const string FSAMarketsFeed = @"http://www.fsa.gov.uk/XMLSchema/FSAMarketsFeed-v1-2";
    public const string FSAFeedCommon = @"http://www.fsa.gov.uk/XMLSchema/FSAFeedCommon-v1-2";

    public static XmlSerializerNamespaces GetFSAMarketsFeedNamespace()
    {
        var ns = new XmlSerializerNamespaces();
        ns.Add("", Namespaces.FSAMarketsFeed);
        return ns;
    }
}

public static class XObjectExtensions
{
    public static T Deserialize<T>(this XContainer element, XmlSerializer serializer)
    {
        using (var reader = element.CreateReader())
        {
            serializer = serializer ?? new XmlSerializer(typeof(T));
            object result = serializer.Deserialize(reader);
            if (result is T)
                return (T)result;
        }
        return default(T);
    }

    public static XDocument SerializeToXDocument<T>(this T obj, XmlSerializer serializer, XmlSerializerNamespaces ns)
    {
        var doc = new XDocument();
        using (var writer = doc.CreateWriter())
        {
            serializer = serializer ?? new XmlSerializer(obj.GetType());
            serializer.Serialize(writer, obj, ns);
        }
        return doc;
    }

    public static XElement SerializeToXElement<T>(this T obj, XmlSerializer serializer, XmlSerializerNamespaces ns)
    {
        var doc = obj.SerializeToXDocument(serializer, ns);
        var element = doc.Root;
        if (element != null)
            element.Remove();
        return element;
    }
}

生成 XML:

<FSAMarketsFeed xmlns="http://www.fsa.gov.uk/XMLSchema/FSAMarketsFeed-v1-2">
  <FSAFeedHeader xmlns="http://www.fsa.gov.uk/XMLSchema/FSAFeedCommon-v1-2">
    <FeedTargetSchemaVersion>value of FeedTargetSchemaVersion</FeedTargetSchemaVersion>
  </FSAFeedHeader>
  <FSAMarketsFeedMsg>
    <CoreItemsMkt xmlns="http://www.fsa.gov.uk/XMLSchema/FSAMarketsFeed-v1-2" />
  </FSAMarketsFeedMsg>
</FSAMarketsFeed>

选项 2:在其包含类型上使用 [XmlAnyElement]CoreMarket 进行嵌套序列化。

使用[XmlAnyElement] 属性,类型可以序列化和反序列化任意子元素。您可以使用此功能对CoreMarket 进行嵌套序列化,其中包含必要的命名空间声明。

为此,修改FSAMarketsFeedMsg如下:

[XmlType(AnonymousType = true)]
public class FSAMarketsFeedMsg
{
    [XmlIgnore]
    public CoreItemsMkt CoreMarket { get; set; }

    [XmlAnyElement(Name = "CoreItemsMkt", Namespace = Namespaces.FSAMarketsFeed)]
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never), DebuggerBrowsable(DebuggerBrowsableState.Never)]
    public XElement CoreMarketXml
    {
        get
        {
            return (CoreMarket == null ? null : XObjectExtensions.SerializeToXElement(CoreMarket, 
                XmlSerializerFactory.Create(typeof(CoreItemsMkt), "CoreItemsMkt", Namespaces.FSAMarketsFeed), 
                Namespaces.GetFSAMarketsFeedNamespace()));
        }
        set
        {
            CoreMarket = (value == null ? null : XObjectExtensions.Deserialize<CoreItemsMkt>(value, 
                XmlSerializerFactory.Create(typeof(CoreItemsMkt), "CoreItemsMkt", Namespaces.FSAMarketsFeed)));
        }
    }

    // Remainder of properties are left unchanged.
}

除了选项 1 中的静态扩展类之外,您还需要以下内容来避免大量 memory leak

public static class XmlSerializerFactory
{
    static readonly Dictionary<Tuple<Type, string, string>, XmlSerializer> table;
    static readonly object padlock;

    static XmlSerializerFactory()
    {
        table = new Dictionary<Tuple<Type, string, string>, XmlSerializer>();
        padlock = new object();
    }

    public static XmlSerializer Create(Type serializedType, string rootName, string rootNamespace)
    {
        if (serializedType == null)
            throw new ArgumentNullException();
        if (rootName == null && rootNamespace == null)
            return new XmlSerializer(serializedType);
        lock (padlock)
        {
            var key = Tuple.Create(serializedType, rootName, rootNamespace);
            XmlSerializer serializer;
            if (!table.TryGetValue(key, out serializer))
            {
                var attr = (string.IsNullOrEmpty(rootName) ? new XmlRootAttribute() { Namespace = rootNamespace } : new XmlRootAttribute(rootName) { Namespace = rootNamespace });
                serializer = table[key] = new XmlSerializer(serializedType, attr);
            }
            return serializer;
        }
    }
}

请注意[XmlAnyElement] 属性将为所有未知元素调用,因此如果您的 XML 出于某种原因包含意外元素,您可能会因为根元素名称错误而从XObjectExtensions.Deserialize 引发异常。如果有可能,您可能希望捕获并忽略此方法的异常。

按照您当前的操作序列化到磁盘。冗余的xmlns= 属性将与选项 1 一样。

【讨论】:

  • @db,这是一个高质量的答案,我认为遇到相同问题的大多数人应该使用这两个选项之一。不幸的是,对于我的情况,选项 1 开始遇到 OutOfMemory 异常(我正在处理数据),而选项 2 破坏了一段超出问题范围的单独代码(我还有一个 excel 文件渲染器运行相同的课程。)。尽管如此,我还是从这篇文章中学到了,并怀疑我以后会使用它。谢谢。
  • @J.Patton - 我很想知道 excel 文件渲染器是如何损坏的。是否有其他不相关的序列化程序尝试序列化 CoreMarketXml 属性?
  • 它生成了一个文件大小合适的 excel 文件,但在 excel 中打开时无法打开(带有“此文件可能已损坏或不完整”错误消息)。这是我几年前自己推出的东西,所以它不是一个非常智能的 excel 文件渲染器。就我而言,使用其他解决方案比返回并修复它更简单。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-09
  • 1970-01-01
  • 2013-03-10
相关资源
最近更新 更多