簡體   English   中英

使用 XmlSerializer 時如何向 XML 文件寫入注釋?

[英]How to write a comment to an XML file when using the XmlSerializer?

我有一個 object Foo,我將其序列化為 XML stream。

public class Foo {
  // The application version, NOT the file version!
  public string Version {get;set;}
  public string Name {get;set;}
}

Foo foo = new Foo { Version = "1.0", Name = "Bar" };
XmlSerializer xmlSerializer = new XmlSerializer(foo.GetType());

這可以快速、簡單地完成當前所需的一切。

我遇到的問題是我需要維護一個帶有一些小注釋的單獨文檔文件。 如上例所示, Name是顯而易見的,但Version是應用程序版本,而不是在這種情況下可以預期的數據文件版本。 我還有更多類似的小事情想通過評論來澄清。

我知道如果我使用WriteComment() function 手動創建 XML 文件,我可以做到這一點,但是我可以實現一個可能的屬性或替代語法,以便我可以繼續使用序列化程序功能嗎?

通過使用返回XmlComment類型對象的屬性並使用[XmlAnyElement("SomeUniquePropertyName")]標記這些屬性,可以使用默認基礎結構來實現這一點。

即,如果您像這樣向Foo添加屬性:

public class Foo
{
    [XmlAnyElement("VersionComment")]
    public XmlComment VersionComment { get { return new XmlDocument().CreateComment("The application version, NOT the file version!"); } set { } }

    public string Version { get; set; }
    public string Name { get; set; }
}

將生成以下 XML:

<Foo>
  <!--The application version, NOT the file version!-->
  <Version>1.0</Version>
  <Name>Bar</Name>
</Foo>

然而,問題要求的不止於此,即在文檔系統中查找評論的某種方式。 以下通過使用擴展方法根據反射的注釋屬性名稱查找文檔來完成此操作:

public class Foo
{
    [XmlAnyElement("VersionXmlComment")]
    public XmlComment VersionXmlComment { get { return GetType().GetXmlComment(); } set { } }

    [XmlComment("The application version, NOT the file version!")]
    public string Version { get; set; }

    [XmlAnyElement("NameXmlComment")]
    public XmlComment NameXmlComment { get { return GetType().GetXmlComment(); } set { } }

    [XmlComment("The application name, NOT the file name!")]
    public string Name { get; set; }
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class XmlCommentAttribute : Attribute
{
    public XmlCommentAttribute(string value)
    {
        this.Value = value;
    }

    public string Value { get; set; }
}

public static class XmlCommentExtensions
{
    const string XmlCommentPropertyPostfix = "XmlComment";

    static XmlCommentAttribute GetXmlCommentAttribute(this Type type, string memberName)
    {
        var member = type.GetProperty(memberName);
        if (member == null)
            return null;
        var attr = member.GetCustomAttribute<XmlCommentAttribute>();
        return attr;
    }

    public static XmlComment GetXmlComment(this Type type, [CallerMemberName] string memberName = "")
    {
        var attr = GetXmlCommentAttribute(type, memberName);
        if (attr == null)
        {
            if (memberName.EndsWith(XmlCommentPropertyPostfix))
                attr = GetXmlCommentAttribute(type, memberName.Substring(0, memberName.Length - XmlCommentPropertyPostfix.Length));
        }
        if (attr == null || string.IsNullOrEmpty(attr.Value))
            return null;
        return new XmlDocument().CreateComment(attr.Value);
    }
}

為其生成以下 XML:

<Foo>
  <!--The application version, NOT the file version!-->
  <Version>1.0</Version>
  <!--The application name, NOT the file name!-->
  <Name>Bar</Name>
</Foo>

筆記:

  • 擴展方法XmlCommentExtensions.GetXmlCommentAttribute(this Type type, string memberName)假定注釋屬性將命名為xxxXmlComment ,其中xxx是“真實”屬性。 如果是這樣,它可以通過使用CallerMemberNameAttribute標記傳入的memberName屬性來自動確定真實的屬性名稱。 這可以通過傳入真實姓名手動覆蓋。

  • 知道類型和成員名稱后,擴展方法通過搜索應用於該屬性的[XmlComment]屬性來查找相關注釋。 這可以用緩存查找替換為單獨的文檔文件。

  • 雖然仍然需要為每個可能被注釋的屬性添加xxxXmlComment屬性,但這可能比直接實現IXmlSerializable負擔更輕,后者非常棘手,可能導致反序列化中的錯誤,並且可能需要復雜子屬性的嵌套序列化.

  • 要確保每個注釋都位於其關聯元素之前,請參閱控制 C# 中的序列化順序

  • 要使XmlSerializer序列化屬性,它必須同時具有 getter 和 setter。 因此,我給出了什么都不做的注釋屬性設置器。

工作.Net 小提琴

無法使用默認基礎架構。 您需要為您的目的實現IXmlSerializable

非常簡單的實現:

public class Foo : IXmlSerializable
{
    [XmlComment(Value = "The application version, NOT the file version!")]
    public string Version { get; set; }
    public string Name { get; set; }


    public void WriteXml(XmlWriter writer)
    {
        var properties = GetType().GetProperties();

        foreach (var propertyInfo in properties)
        {
            if (propertyInfo.IsDefined(typeof(XmlCommentAttribute), false))
            {
                writer.WriteComment(
                    propertyInfo.GetCustomAttributes(typeof(XmlCommentAttribute), false)
                        .Cast<XmlCommentAttribute>().Single().Value);
            }

            writer.WriteElementString(propertyInfo.Name, propertyInfo.GetValue(this, null).ToString());
        }
    }
    public XmlSchema GetSchema()
    {
        throw new NotImplementedException();
    }

    public void ReadXml(XmlReader reader)
    {
        throw new NotImplementedException();
    }
}

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class XmlCommentAttribute : Attribute
{
    public string Value { get; set; }
}

輸出:

<?xml version="1.0" encoding="utf-16"?>
<Foo>
  <!--The application version, NOT the file version!-->
  <Version>1.2</Version>
  <Name>A</Name>
</Foo>

另一種可能更可取的方法是:使用默認序列化程序進行序列化,然后執行后處理,即更新 XML,例如使用XDocumentXmlDocument

序列化后在xml末尾添加注釋(魔術是刷新xmlWriter)。

byte[] buffer;

XmlSerializer serializer = new XmlSerializer(result.GetType());

var settings = new XmlWriterSettings() { Encoding = Encoding.UTF8 };

using (MemoryStream memoryStream = new MemoryStream())
{
    using (XmlWriter xmlWriter = XmlWriter.Create(memoryStream, settings))
    {
        serializer.Serialize(xmlWriter, result);

        xmlWriter.WriteComment("test");

        xmlWriter.Flush();

        buffer = memoryStream.ToArray();
    }
}

可能遲到了,但是當我嘗試使用 Kirill Polishchuk 解決方案反序列化時遇到了問題。 最后我決定在序列化后編輯 XML,解決方案如下:

public static void WriteXml(object objectToSerialize, string path)
{
    try
    {
        using (var w = new XmlTextWriter(path, null))
        {
            w.Formatting = Formatting.Indented;
            var serializer = new XmlSerializer(objectToSerialize.GetType());
            serializer.Serialize(w, objectToSerialize);
        }

        WriteComments(objectToSerialize, path);
    }
    catch (Exception e)
    {
        throw new Exception($"Could not save xml to path {path}. Details: {e}");
    }
}

public static T ReadXml<T>(string path) where T:class, new()
{
    if (!File.Exists(path))
        return null;
    try
    {
        using (TextReader r = new StreamReader(path))
        {
            var deserializer = new XmlSerializer(typeof(T));
            var structure = (T)deserializer.Deserialize(r);
            return structure;
        }
    }
    catch (Exception e)
    {
        throw new Exception($"Could not open and read file from path {path}. Details: {e}");
    }
}

private static void WriteComments(object objectToSerialize, string path)
{
    try
    {
        var propertyComments = GetPropertiesAndComments(objectToSerialize);
        if (!propertyComments.Any()) return;

        var doc = new XmlDocument();
        doc.Load(path);

        var parent = doc.SelectSingleNode(objectToSerialize.GetType().Name);
        if (parent == null) return;

        var childNodes = parent.ChildNodes.Cast<XmlNode>().Where(n => propertyComments.ContainsKey(n.Name));
        foreach (var child in childNodes)
        {
            parent.InsertBefore(doc.CreateComment(propertyComments[child.Name]), child);
        }

        doc.Save(path);
    }
    catch (Exception)
    {
        // ignored
    }
}

private static Dictionary<string, string> GetPropertiesAndComments(object objectToSerialize)
{
    var propertyComments = objectToSerialize.GetType().GetProperties()
        .Where(p => p.GetCustomAttributes(typeof(XmlCommentAttribute), false).Any())
        .Select(v => new
        {
            v.Name,
            ((XmlCommentAttribute) v.GetCustomAttributes(typeof(XmlCommentAttribute), false)[0]).Value
        })
        .ToDictionary(t => t.Name, t => t.Value);
    return propertyComments;
}

[AttributeUsage(AttributeTargets.Property)]
public class XmlCommentAttribute : Attribute
{
    public string Value { get; set; }
}

用戶 dbc 提出的解決方案看起來不錯,但是與使用知道如何根據 XmlComment 屬性插入注釋的 XmlWriter 相比,創建此類注釋似乎需要更多的手動工作。

請參閱https://archive.codeplex.com/?p=xmlcomment - 似乎您可以將這樣的編寫器傳遞給 XmlSerializer,因此不必實現您自己的序列化,這可能很棘手。

不過,我自己最終還是使用了 dbc 的解決方案,既漂亮又干凈,沒有額外的代碼。 請參閱https://dotnetfiddle.net/Bvbi0N 確保為注釋元素(XmlAnyElement)提供了一個“set”訪問器。 順便說一句,它不需要有名字。

更新:最好始終傳遞唯一名稱,也就是使用 [XmlAnyElement("someCommentElement")] 而不是 [XmlAnyElement]。 在 WCF 中使用相同的類,並且它被那些沒有提供名稱的 XmlAnyElements 窒息,即使我有 [XmlIgnore、SoapIgnore、IgnoreDataMember]。

對於嵌套的 xml,我以這種方式更改了方法(對我來說,我將簡單的屬性作為字符串(它可能使其在邏輯中更復雜)

public void WriteXml(XmlWriter writer)
    {
        var properties = GetType().GetProperties();

        foreach (var propertyInfo in properties)
        {
            if (propertyInfo.IsDefined(typeof(XmlCommentAttribute), false))
            {
                writer.WriteComment(
                    propertyInfo.GetCustomAttributes(typeof(XmlCommentAttribute), false)
                        .Cast<XmlCommentAttribute>().Single().Value);
            }

                if (propertyInfo.GetValue(this, null).GetType().ToString() != "System.String")
                {
                    XmlSerializer xmlSerializer = new XmlSerializer(propertyInfo.GetValue(this, null).GetType());
                    xmlSerializer.Serialize(writer, propertyInfo.GetValue(this, null));
                }
                else
                {
                    writer.WriteElementString(propertyInfo.Name, propertyInfo.GetValue(this, null).ToString());

                }
            }
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM