使用XmlSerializer时如何在XML文件中写注释?

时间:2011-09-12 09:42:39

标签: c# .net xml xml-serialization xmlserializer

我有一个对象Foo,我将其序列化为XML流。

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()函数手动创建我的XML文件,我可以这样做,但是我可以实现一个可能的属性或替代语法,以便我可以继续使用序列化程序功能吗?

5 个答案:

答案 0 :(得分:14)

通过使用返回类型为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)假定comment属性的名称为xxxXmlComment,其中xxx是“真实”属性。如果是这样,它可以通过使用CallerMemberNameAttribute标记传入的memberName属性来自动确定不动产名称。这可以通过传入真实姓名手动覆盖。

  • 一旦知道了类型和成员名称,扩展方法就会通过搜索应用于该属性的[XmlComment]属性来查找相关注释。这可以用缓存查找替换为单独的文档文件。

  • 虽然仍然需要为每个可能被评论的属性添加xxxXmlComment属性,但这可能不如implementing IXmlSerializable directly那么繁琐,这非常棘手,可能导致错误在反序列化中,可能需要复杂子属性的嵌套序列化。

  • 要确保每条评论都在其关联元素之前,请参阅Controlling order of serialization in C#

  • 对于XmlSerializer序列化属性,它必须同时具有getter和setter。因此,我给评论属性设置者什么都不做。

工作.Net fiddle

答案 1 :(得分:12)

无法使用默认基础架构。您需要为您的目的实施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

答案 2 :(得分:0)

可能迟到了派对,但是当我尝试使用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; }
}

答案 3 :(得分:0)

序列化后在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();
    }
}

答案 4 :(得分:0)

用户dbc提出的solution看起来不错,但是与使用XmlWriter知道如何基于XmlComment属性插入注释的方法相比,创建此类注释似乎需要更多的手动工作。

请参见https://archive.codeplex.com/?p=xmlcomment-看来您可以将这样的编写器传递给XmlSerializer,因此不必实现可能很棘手的自己的序列化。

尽管如此,我还是最终使用了dbc的解决方案,而且很干净,没有任何额外的代码。参见https://dotnetfiddle.net/Bvbi0N。确保为注释元素(XmlAnyElement)提供一个“设置”访问器。不需要名字btw。

更新:最好始终传递一个唯一名称,也可以使用[XmlAnyElement(“ someCommentElement”)]而不是[XmlAnyElement]。在WCF上使用了同一类,即使没有[XmlIgnore,SoapIgnore,IgnoreDataMember],也没有提供名称的XmlAnyElement令人窒息。