问题
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:
- A generic class to hold both our main property value and the metadata (see
PropertyWithAppliesTo<T>) - Using
XmlAttributeAttributeon the generic class' metadata, so it is written as an XML attribute on the parent property - Using
XmlTextAttributeon the generic class' main data, so it is written as the Xml text of the parent property (and not as a sub-property) - 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 withXmlIgnoreAttributethat provides 'expected' access to the property's value - Using the
XmlElementAttributeto 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 typePropertyWithAppliesTo<T>marked withXmlElementto give it the name you want, and then a property of type T marked withXmlIgnorethat wraps around theValueyou want. - You can control the value of
AppliesToby changing the input to the constructor ofPropertyWithAppliesTo<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