XML serialisation for class properties with additional meta data

ぐ巨炮叔叔 提交于 2021-02-10 14:22:59

问题


I have an entity as below

public class Vehicle{
    public int VehicleId {get;set;};
    public string Make {get;set;};
    public string Model{get;set;}
}

I wanted to serialize as below

<Vehicle>
   <VehicleId AppliesTo="C1">1244</VehicleId>
   <Make AppliesTo="Common" >HXV</Make>
   <Model AppliesTo="C2">34-34</Model>
</Vehicle>

I have around 100 properties like this in Vehicle class, for each vehicle property I wanted to attach a metadata ApplieTo which will be helpful to downstream systems. AppliesTo attribute is static and its value is defined at the design time. Now How can I attach AppliesTo metadata to each property and inturn get serialized to XML?


回答1:


You can use XElement from System.Xml.Linq to achieve this. As your data is static you can assign them easily. Sample code below -

XElement data= new XElement("Vehicle",
               new XElement("VehicleId", new XAttribute("AppliesTo", "C1"),"1244"),
               new XElement("Make", new XAttribute("AppliesTo", "Common"), "HXV"),
               new XElement("Model", new XAttribute("AppliesTo", "C2"), "34 - 34")
               );
  //OUTPUT
  <Vehicle>
   <VehicleId AppliesTo="C1">1244</VehicleId>
   <Make AppliesTo="Common">HXV</Make>
   <Model AppliesTo="C2">34 - 34</Model>
  </Vehicle>

If you are not interested in System.Xml.Linq then you have another option of XmlSerializer class. For that you need yo define separate classes for each property of vehicle. Below is the sample code and you can extend the same for Make and Model -

[XmlRoot(ElementName = "VehicleId")]
public class VehicleId
{
    [XmlAttribute(AttributeName = "AppliesTo")]
    public string AppliesTo { get; set; }
    [XmlText]
    public string Text { get; set; }
}


[XmlRoot(ElementName = "Vehicle")]
public class Vehicle
{
    [XmlElement(ElementName = "VehicleId")]
    public VehicleId VehicleId { get; set; }
    //Add other properties here
}

Then create test data and use XmlSerializer class to construct XML -

Vehicle vehicle = new Vehicle
         {
            VehicleId = new VehicleId
              {
                 Text = "1244",
                 AppliesTo = "C1",
              }
         };

XmlSerializer testData = new XmlSerializer(typeof(Vehicle));            
var xml = "";

using (var sww = new StringWriter())
   {
      using (XmlWriter writer = XmlWriter.Create(sww))
       {
          testData.Serialize(writer, vehicle);
          xml = sww.ToString(); // XML 
       }
    }



回答2:


It is not easy or ideal to use the default .NET XML serializer (System.Xml.Serialization.XmlSerializer) in the way you want, but it's possible. This answer shows how to create a class structure to hold both your main data and the metadata, then use XmlAttributeAttribute to mark a property so it gets serialized as an XML attribute.

Assumptions:

There are a number of unknowns about your intended implementation, such as:

  • The XML serializer you want to use (default for .NET?)
  • The mechanism to inject 'AppliesTo' (attribute?)
  • Do you care about deserialization?

This answer assumes the default .NET serializer, that deserialization matters, and that you don't care about the exact method of injecting your metadata.

Key concepts:

  1. A generic class to hold both our main property value and the metadata (see PropertyWithAppliesTo<T>)
  2. Using XmlAttributeAttribute on the generic class' metadata, so it is written as an XML attribute on the parent property
  3. Using XmlTextAttribute on the generic class' main data, so it is written as the Xml text of the parent property (and not as a sub-property)
  4. Including two properties on the main type being serialized (in this case Vehicle) for every value you want serialized: one of the new generic type that gets serialized with metadata, and one of the original type marked with XmlIgnoreAttribute that provides 'expected' access to the property's value
  5. Using the XmlElementAttribute to change the name of the serialized property (so it matches the expected name)

Code:

using System;
using System.IO;
using System.Xml.Serialization;

namespace SomeNamespace
{
    public class Program
    {
        static void Main()
        {
            var serializer = new XmlSerializer(typeof(Vehicle));
            string s;

            var vehicle = new Vehicle { VehicleId = 1244 };

            //serialize
            using (var writer = new StringWriter())
            {
                serializer.Serialize(writer, vehicle);
                s = writer.ToString();
                Console.WriteLine(s);
            }

            // edit the serialized string to test deserialization
            s = s.Replace("Common", "C1");

            //deserialize
            using (var reader = new StringReader(s))
            {
                vehicle = (Vehicle)serializer.Deserialize(reader);
                Console.WriteLine($"AppliesTo attribute for VehicleId: {vehicle.VehicleIdMeta.AppliesTo}");
            }
        }
    }

    public class Vehicle
    {
        [XmlElement(ElementName = "VehicleId")] // renames to remove the 'Meta' string
        public PropertyWithAppliesTo<int> VehicleIdMeta { get; set; } = new PropertyWithAppliesTo<int>("Common");

        [XmlIgnore] // this value isn't serialized, but the property here for easy syntax
        public int VehicleId
        {
            get { return VehicleIdMeta.Value; }
            set { VehicleIdMeta.Value = value; }
        }
    }

    public class PropertyWithAppliesTo<T>
    {
        [XmlAttribute] // tells serializer this should be an attribute on this element, not a property
        public string AppliesTo { get; set; } = string.Empty;
        [XmlText] // tells serializer to not write this as a property, but as the main XML text
        public T Value { get; set; } = default;

        public PropertyWithAppliesTo() : this(string.Empty) { }
        public PropertyWithAppliesTo(string appliesTo) : this(appliesTo, default) { }
        public PropertyWithAppliesTo(string appliesTo, T initialValue)
        {
            AppliesTo = appliesTo;
            Value = initialValue;
        }
    }
}

When run, the string s will look like:

<?xml version=\"1.0\" encoding=\"utf-16\"?>
<Vehicle xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\">
    <VehicleId AppliesTo="Common">1244</VehicleId>
</Vehicle>

Other Notes:

  • You can see how to add more properties to Vehicle: add a property of type PropertyWithAppliesTo<T> marked with XmlElement to give it the name you want, and then a property of type T marked with XmlIgnore that wraps around the Value you want.
  • You can control the value of AppliesTo by changing the input to the constructor of PropertyWithAppliesTo<T> and giving it a different metadata string.
  • If you don't want consumers of your library to see the 'meta' properties in IntelliSense, you can use the EditorBrowsableAttribute. It won't hide things from you when using the source and a project reference; it's only hidden when referencing the compiled dll.

This is admittedly an annoying way to add properties to a class. But if you want to use the default .NET XML serializer, this is a way to achieve the XML you want.



来源:https://stackoverflow.com/questions/64431164/xml-serialisation-for-class-properties-with-additional-meta-data

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!