XML class 属性的序列化和额外的元数据

XML serialisation for class properties with additional meta data

我有一个实体如下

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

我想连载如下

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

我在 Vehicle class 中有大约 100 个这样的属性,对于每辆车 属性 我想附加一个元数据 ApplieTo,这将有助于下游系统。 AppliesTo 属性是静态的,它的值是在设计时定义的。现在如何将 AppliesTo 元数据附加到每个 属性 并依次序列化为 XML?

您可以使用 System.Xml.Linq 中的 XElement 来实现此目的。由于您的数据是静态的,因此您可以轻松分配它们。下面的示例代码 -

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>

如果您对 System.Xml.Linq 不感兴趣,那么您还有另一个选择 XmlSerializer class。为此,您需要为 vehicle 的每个 属性 定义单独的 classes。下面是示例代码,您可以将其扩展为 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
}

然后创建测试数据,用XmlSerializerclass构造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 
       }
    }

使用默认的 .NET XML 序列化程序(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 来标记 属性 因此它被序列化为 XML 属性并不容易或不理想。

假设:

关于您的预期实施有许多未知数,例如:

  • 您要使用的 XML 序列化程序(.NET 的默认设置?)
  • 注入机制'AppliesTo'(属性?)
  • 你关心反序列化吗?

此答案假定默认的 .NET 序列化程序,反序列化很重要,并且您不关心注入元数据的确切方法。

关键概念:

  1. 通用 class 来保存我们的主要 属性 值和元数据(参见 PropertyWithAppliesTo<T>
  2. 在通用 class' 元数据上使用 XmlAttributeAttribute,因此它被写为父 属性
  3. 上的 XML 属性
  4. 在通用 class' 主数据上使用 XmlTextAttribute,因此它被写为父 属性 的 Xml 文本(而不是 sub-property)
  5. 在要序列化的每个值的要序列化的主要类型(在本例中为 Vehicle)上包括两个属性:一种是使用元数据序列化的新通用类型,另一种是标记为原始类型XmlIgnoreAttribute 提供 'expected' 访问 属性 的值
  6. 使用 XmlElementAttribute 更改序列化的名称 属性(因此它与预期名称匹配)

代码:

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

当 运行 时,字符串 s 将如下所示:

<?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>

其他注意事项:

  • 您可以看到如何向 Vehicle 添加更多属性:添加一个 PropertyWithAppliesTo<T> 类型的 属性 标记为 XmlElement 并为其指定您想要的名称,然后然后是标有 XmlIgnore 的 T 类型的 属性,它环绕着您想要的 Value
  • 您可以通过更改 PropertyWithAppliesTo<T> 的构造函数的输入并为其提供不同的元数据字符串来控制 AppliesTo 的值。
  • 如果您不希望库的使用者在 IntelliSense 中看到 'meta' 属性,您可以使用 EditorBrowsableAttribute。在使用源代码和项目引用时,它不会向您隐瞒任何事情;它仅在引用已编译的 dll 时隐藏。

无可否认,这是一种向 class 添加属性的恼人方式。但是如果你想使用默认的 .NET XML 序列化程序,这是一种实现你想要的 XML 的方法。