¿Cómo escribir un comentario en un archivo XML cuando se utiliza el XmlSerializer?

Tengo un objeto Foo que serializo a una secuencia 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()); 

Esto funciona rápido, fácil y hace todo lo que se requiere actualmente.

El problema que tengo es que necesito mantener un archivo de documentación separado con algunas observaciones menores. Como en el ejemplo anterior, Name es obvio, pero Version es la versión de la aplicación y no la versión del archivo de datos como podría esperarse en este caso. Y tengo muchas cosas más similares que quiero aclarar con un comentario.

Sé que puedo hacer esto si manualmente creo mi archivo XML usando la función WriteComment() , pero ¿hay algún atributo posible o una syntax alternativa que pueda implementar para poder seguir usando la funcionalidad del serializador?

No es posible usar la infraestructura predeterminada. Necesita implementar IXmlSerializable para sus propósitos.

Implementación muy simple:

 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().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; } } 

Salida:

    1.2 A  

Otra forma, quizás preferible: serializar con el serializador predeterminado, luego realizar un postprocesamiento, es decir, actualizar XML, por ejemplo, utilizando XDocument o XmlDocument .

Esto es posible utilizando la infraestructura predeterminada mediante el uso de propiedades que devuelven un objeto de tipo XmlComment y marcando esas propiedades con [XmlAnyElement("SomeUniquePropertyName")] .

Es decir, si agrega una propiedad a Foo esta manera:

 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; } } 

Se generará el siguiente XML:

   1.0 Bar  

Sin embargo, la pregunta es pedir más que esto, es decir, alguna forma de buscar el comentario en un sistema de documentación. Lo siguiente lo logra usando métodos de extensión para buscar la documentación en función del nombre de propiedad del comentario reflejado:

 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(); 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); } } 

Para lo cual se genera el siguiente XML:

   1.0  Bar  

Notas:

  • El método de extensión XmlCommentExtensions.GetXmlCommentAttribute(this Type type, string memberName) asume que la propiedad de comentario se llamará xxxXmlComment donde xxx es la propiedad “real”. Si es así, puede determinar automáticamente el nombre de la propiedad real marcando el atributo memberName entrante con CallerMemberNameAttribute . Esto puede anularse manualmente al pasar el nombre real.

  • Una vez que se conoce el tipo y el nombre del miembro, el método de extensión busca el comentario relevante buscando un atributo [XmlComment] aplicado a la propiedad. Esto podría reemplazarse con una búsqueda en caché en un archivo de documentación separado.

  • Si bien aún es necesario agregar las propiedades xxxXmlComment para cada propiedad que podría comentarse, es probable que esto sea menos oneroso que implementar IXmlSerializable directamente, lo cual es bastante complicado, puede provocar errores en la deserialización y puede requerir la serialización anidada de propiedades secundarias complejas. .

  • Para asegurarse de que cada comentario preceda a su elemento asociado, consulte Controlar el orden de la serialización en C # .

  • Para que XmlSerializer una propiedad, debe tener un getter y un setter. Por lo tanto, le di al conjunto de propiedades de comentarios que no hacen nada.

Trabajo .Net violín .

Probablemente llegó tarde a la fiesta, pero tuve problemas cuando intentaba deserializar el uso de la solución de Kirill Polishchuk. Finalmente decidí editar el XML después de serializarlo y la solución se ve así:

 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(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().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 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; } }