C# XML Serializer won't store an attribute - c#

This is my first question on Stack Overflow. Apologies in advance if I don't do things quite right while I'm learning how things work here.
Here is my code :
public void TestSerialize()
{
ShoppingBag _shoppingBag = new ShoppingBag();
Fruits _fruits = new Fruits();
_fruits.testAttribute = "foo";
Fruit[] fruit = new Fruit[2];
fruit[0] = new Fruit("pineapple");
fruit[1]= new Fruit("kiwi");
_fruits.AddRange(fruit);
_shoppingBag.Items = _fruits;
Serialize<ShoppingBag>(_shoppingBag, #"C:\temp\shopping.xml");
}
public static void Serialize<T>(T objectToSerialize, string filePath) where T : class
{
XmlSerializer serializer = new XmlSerializer(typeof(T));
using (StreamWriter writer = new StreamWriter(filePath))
{
serializer.Serialize(writer, objectToSerialize);
}
}
[Serializable]
public class ShoppingBag
{
private Fruits _items;
public Fruits Items
{
get { return _items; }
set {_items = value; }
}
}
public class Fruits : List<Fruit>
{
public string testAttribute { get; set; }
}
[Serializable]
public class Fruit
{
public Fruit() { }
public Fruit(string value)
{
Name = value;
}
[XmlAttribute("name")]
public string Name { get; set; }
}
It produces this XML :
<?xml version="1.0" encoding="utf-8" ?>
<ShoppingBag xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Items>
<Fruit name="pineapple" />
<Fruit name="kiwi" />
</Items>
</ShoppingBag>
I don't understand why I am not getting <Items testAttribute="foo">
Please can anyone tell me what I need to add to my code so that the Serializer will write this attribute out?
Thanks,

You need an intermediary class there:
class Program
{
static void Main()
{
var shoppingBag = new ShoppingBag
{
Items = new ShoppingBagItems
{
Fruits = new List<Fruit>(new[] {
new Fruit { Name = "pineapple" },
new Fruit { Name = "kiwi" },
}),
TestAttribute = "foo"
}
};
var serializer = new XmlSerializer(typeof(ShoppingBag));
serializer.Serialize(Console.Out, shoppingBag);
}
}
public class ShoppingBag
{
public ShoppingBagItems Items { get; set; }
}
public class ShoppingBagItems
{
[XmlElement("Fruit")]
public List<Fruit> Fruits { get; set; }
[XmlAttribute("testAttribute")]
public string TestAttribute { get; set; }
}
public class Fruit
{
[XmlAttribute("name")]
public string Name { get; set; }
}
Also note that you don't need to decorate your classes with the [Serializable] attribute as it is used only for binary serialization. Another remark is that you don't need to derive from List<T>, simply use it as a property.

Unfortunately, when serializing a collection the XmlSerializer doesn't take into account the extra properties of that collection. It only considers the members that implement ICollection<T>. If you want to serialize extra attributes, you need to wrap the collection in another class that is not a collection itself.

Related

Incorrect XML structure c# [duplicate]

I am having an issue with serializing and object, I can get it to create all the correct outputs except for where i have an Element that needs a value and an attribute. Here is the required output:
<Root>
<Method>Retrieve</Method>
<Options>
<Filter>
<Times>
<TimeFrom>2009-06-17</TimeFrom>
</Times>
<Document type="word">document name</Document>
</Filter>
</Options>
</AdCourierAPI>
I can build all of it but can not find a way to set the Document type attribute, here is a segment of the object class
[XmlRoot("Root"), Serializable]
public class Root
{
[XmlElement("Method")]
public string method="RetrieveApplications";
[XmlElement("Options")]
public _Options Options;
}
public class _Options
{
[XmlElement("Filter")]
public _Filter Filter;
}
public class _Filter
{
[XmlElement("Times")]
public _Times Times;
[XmlElement("Documents")]
public string Documents;
}
which gives me:
<Document>document name</Document>
rather than:
<Document type="word">document name</Document>
but I can not find a way to correct this, please advise.
Thanks
Where do you have the type stored?
Normally you could have something like:
class Document {
[XmlAttribute("type")]
public string Type { get; set; }
[XmlText]
public string Name { get; set; }
}
public class _Filter
{
[XmlElement("Times")]
public _Times Times;
[XmlElement("Document")]
public Document Document;
}
The string class doesn't have a type property, so you can't use it to create the desired output. You should create a Document class instead :
public class Document
{
[XmlText]
public string Name;
[XmlAttribute("type")]
public string Type;
}
And you should change the Document property to type Document
It sounds like you need an extra class:
public class Document
{
[XmlAttribute("type")]
public string Type { get; set; }
[XmlText]
public string Name { get; set; }
}
Where an instance (in the example) would have Type = "word" and Name = "document name"; documents would be a List<Document>.
By the way - public fields are rarely a good idea...
You can use XmlWriter instead XmlSerialization to get this effect.
It is more complex but if you have a lot of strings in model it will be cleaner solution.
Create your own CustomeAttribute, for example:
[System.AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class MyCustomAttribute : System.Attribute
{
public MyCustomAttribute (string type)
{
MyType = type;
}
public string MyType { get; set; }
}
Then in model add it, like that:
public class MyModel
{
[MyCustom("word")]
public string Document { get; set; }
[MyCustom("time")]
public string Time { get; set; }
}
The last part is to create xml with this arguments.
You can do it likes that:
var doc = new XmlDocument();
MyModel myModel = new MyModel();//or get it from somewhere else
using (Stream s = new MemoryStream())
{
var settings = new XmlWriterSettings();
settings.Async = true;
settings.Indent = true;
var writer = XmlTextWriter.Create(s, settings);
await writer.WriteStartDocumentAsync();
await writer.WriteStartElementAsync(null,"Root", null);
myModel.GetType().GetProperties().ToList().ForEach(async p =>
{
dynamic value = p.GetValue(myModel);
writer.WriteStartElement(p.Name);
var myCustomAttribute = p.GetCustomAttributes(typeof(MyCustomAttribute), false).FirstOrDefault() as MyCustomAttribute;
if(myCustomAttribute != null)
{
await writer.WriteAttributeStringAsync(null, "MyType", null, myCustomAttribute.MyType );
}
writer.WriteValue(value);
await writer.WriteEndElementAsync();
});
await writer.WriteEndElementAsync();
await writer.FlushAsync();
s.Position = 0;
doc.Load(s);
writer.Close();
}
string myXml = doc.OuterXml
In myXml should be something like that:
(values are examples)
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Document MyType="word">something</Document>
<Time MyType="time">11:31:29</Time>
</Root>
You can do it in other way, of course.
Here you have some docs which helped me:
https://learn.microsoft.com/en-us/dotnet/api/system.xml.xmlwriter?view=netframework-4.8#writing_elements

XML Serializing information on Base class and inherited class

I am trying to serialize a C# object into XML so that it could be used as the body of API call. They are very particular about the input they need. I have built the following class to hold the data I need to send over to them. Including attributes and all properties as Elements instead of attributes. They also require that lists include the type="array" I though that creating my own class the implements a List would be the easiest since all lists I give them must have the same attribute. When serialization occurs it serializes the base class of List items but it doesn't include the attribute I want from the derived class.
public class CustomArray<T> : List<T>
{
[XmlAttribute]
public string type { get; set; } = "array";
}
[XmlRoot("message")]
public class MessageBody
{
[XmlArray("Checks"), XmlArrayItem("CheckItem")]
public CustomArray<Check> CheckList { get; set; }
}
public class Check
{
[XmlElement("C_CHECK_NUMBER")]
public string CheckNumber { get; set; }
[XmlElement("C_CHECK_AMOUNT")]
public decimal Amount { get; set; }
[XmlArray("InvoiceList"), XmlArrayItem("Invoice")]
public CustomArray<Invoice> InvoiceList { get; set; }
}
public class Invoice
{
[XmlElement("C_INVOICE_ID")]
public long ID { get; set; }
[XmlElement("C_INVOICE_NUM")]
public string InvoiceNum { get; set; }
}
I then run this code:
// Create a sample object
var message = new MessageBody()
{
CheckList = new CustomArray<Check>
{
new Check
{
CheckNumber = "111",
Amount = 1.00M
},
new Check
{
CheckNumber = "112",
Amount = 2.00M,
InvoiceList = new CustomArray<Invoice>
{
new Invoice
{
ID = 1,
InvoiceNum = "1"
}
}
}
}
};
// Create custom settings
var settings = new XmlWriterSettings
{
OmitXmlDeclaration = true,
Indent = true
};
// Serialize item and print it to console
using (var sw = new StringWriter())
using (var writer = XmlWriter.Create(sw, settings))
{
var serializer = new XmlSerializer(message.GetType());
serializer.Serialize(writer, message, new XmlSerializerNamespaces(new[] { XmlQualifiedName.Empty }));
Console.WriteLine(sw.ToString());
}
I get this written to the console:
<message>
<Checks>
<CheckItem>
<C_CHECK_NUMBER>111</C_CHECK_NUMBER>
<C_CHECK_AMOUNT>1.00</C_CHECK_AMOUNT>
</CheckItem>
<CheckItem>
<C_CHECK_NUMBER>112</C_CHECK_NUMBER>
<C_CHECK_AMOUNT>2.00</C_CHECK_AMOUNT>
<InvoiceList>
<Invoice>
<C_INVOICE_ID>1</C_INVOICE_ID>
<C_INVOICE_NUM>1</C_INVOICE_NUM>
</Invoice>
</InvoiceList>
</CheckItem>
</Checks>
</message>
But I need to get this:
<message>
<Checks type="array">
<CheckItem>
<C_CHECK_NUMBER>111</C_CHECK_NUMBER>
<C_CHECK_AMOUNT>1.00</C_CHECK_AMOUNT>
</CheckItem>
<CheckItem>
<C_CHECK_NUMBER>112</C_CHECK_NUMBER>
<C_CHECK_AMOUNT>2.00</C_CHECK_AMOUNT>
<InvoiceList type="array">
<Invoice>
<C_INVOICE_ID>1</C_INVOICE_ID>
<C_INVOICE_NUM>1</C_INVOICE_NUM>
</Invoice>
</InvoiceList>
</CheckItem>
</Checks>
</message>
Thank you for your help!
Here is a dotnetfiddle that I made to show it off. It's not exact but it has the same idea. https://dotnetfiddle.net/ALCX5H
Try following :
[XmlRoot("message")]
public class MessageBody
{
[XmlElement("Checks")]
public Checks Checks { get; set; }
}
public class Checks
{
[XmlAttribute]
public string type { get; set; }
[XmlElement("Checks")]
public List<Check> Checks { get; set; }
}
public class Check
{
[XmlElement("C_CHECK_NUMBER")]
public string CheckNumber { get; set; }
[XmlElement("C_CHECK_AMOUNT")]
public decimal Amount { get; set; }
}

C# - XML serialization: omitting a certain element in my XML output

I have a weird XML setup here: I need to interface with a third-party and their XML layout that they're using is beyond my control - no chance to changing anything...
In the scope of a larger XML, I find myself needing to serialize (and later also deserialize) a list of items (for a mobile device) which are defined as having a Type and a Number property (both strings). So this would be something like:
public class SerialNumber
{
public string Type { get; set; }
public string Number { get; set; }
}
And this would normally serialize a List<SerialNumber> SerialNumbers as
<SerialNumbers>
<SerialNumber>
<Type>SN</Type>
<Number>CBS583ABC123</Number>
</SerialNumber>
<SerialNumber>
<Type>IMEI</Type>
<Number>35-924106-659945-4</Number>
</SerialNumber>
</SerialNumbers>
However, in my case, I need to send this XML:
<SerialNumbers>
<Type>SN</Type>
<Number>CBS583ABC123</Number>
<Type>IMEI</Type>
<Number>35-924106-659945-4</Number>
</SerialNumbers>
So basically, the list elements need to be omitted - I just need a container <SerialNumbers> and then for each entry in the list, I only need to serialize out the Type and Number subelements.
How can I do this easily in .NET with the XmlSerializer ?
I tried to use
[XmlRoot(ElementName="")]
public class SerialNumber
or
[XmlArray]
[XmlArrayItem(ElementName = "")]
public List<SerialNumber> SerialNumbers { get; set; }
but neither of these worked - I still get my full serialization with the <SerialNumber> elements inside the <SerialNumbers> container...
Is there an easy trick to achieve what I'm looking for? I'd much rather not go low-level and start concetanating together my XML manually....
Thanks!
You could use custom serialization with IXmlSerializable.
public class SerialNumbers : List<SerialNumber>, IXmlSerializable
{
public XmlSchema GetSchema()
{
return null;
}
public void ReadXml(XmlReader reader)
{
Clear();
reader.ReadStartElement();
while (reader.NodeType != XmlNodeType.EndElement)
{
var serialNumber = new SerialNumber
{
Type = reader.ReadElementContentAsString("Type", ""),
Number = reader.ReadElementContentAsString("Number", "")
};
Add(serialNumber);
}
reader.ReadEndElement();
}
public void WriteXml(XmlWriter writer)
{
foreach (var serialNumber in this)
{
writer.WriteElementString("Type", serialNumber.Type);
writer.WriteElementString("Number", serialNumber.Number);
}
}
}
public class SerialNumber
{
public string Type { get; set; }
public string Number { get; set; }
}
Example:
var ser = new XmlSerializer(typeof(SerialNumbers));
var reader = new StringReader(#"
<SerialNumbers>
<Type>SN</Type>
<Number>CBS583ABC123</Number>
<Type>IMEI</Type>
<Number>35-924106-659945-4</Number>
</SerialNumbers>
".Trim());
var result = (SerialNumbers) ser.Deserialize(reader);
var writer = new StringWriter();
ser.Serialize(writer, result);
Result:
<?xml version="1.0" encoding="utf-16"?>
<SerialNumbers>
<Type>SN</Type>
<Number>CBS583ABC123</Number>
<Type>IMEI</Type>
<Number>35-924106-659945-4</Number>
</SerialNumbers>
This might do the trick for you
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class SerialNumbers
{
private string[] itemsField;
private ItemsChoiceType[] itemsElementNameField;
[System.Xml.Serialization.XmlElementAttribute("Number", typeof(string))]
[System.Xml.Serialization.XmlElementAttribute("Type", typeof(string))]
[System.Xml.Serialization.XmlChoiceIdentifierAttribute("ItemsElementName")]
public string[] Items
{
get
{
return this.itemsField;
}
set
{
this.itemsField = value;
}
}
[System.Xml.Serialization.XmlElementAttribute("ItemsElementName")]
[System.Xml.Serialization.XmlIgnoreAttribute()]
public ItemsChoiceType[] ItemsElementName
{
get
{
return this.itemsElementNameField;
}
set
{
this.itemsElementNameField = value;
}
}
}
[System.Xml.Serialization.XmlTypeAttribute(IncludeInSchema = false)]
public enum ItemsChoiceType
{
Number,
Type,
}

Serializing class implementing ICollection

I have a collection class implementing ICollection<T> with a few custom attributes thrown in for completeness..
In this simplistic sample, its a simple Request/Results pattern with the request itself being passed back as an attribute of the results class.
[Serializable]
public class MyRequest
{
public int SearchID { get; set; }
}
[Serializable]
public class MyResults : ICollection<MyElement>
{
public MyRequest RequestDetails { get; set; }
private ICollection<MyElement> _list = new List<MyElement>();
/* ICollection interface methods removed */
}
[Serializable]
public class MyElement
{
public int ID { get; set; }
}
Here's the sample program to instantiate and then output a serialized string.
class Program
{
static void Main(string[] args)
{
MyResults m = new MyResults();
m.RequestDetails = new MyRequest() { SearchID = 1 };
for (int i = 1; i <= 5; i++)
{
m.Add(new MyElement { ID = i });
}
XmlDocument xmlDoc = new XmlDocument();
XmlSerializer xmlSerializer = new XmlSerializer(m.GetType());
using (MemoryStream xmlStream = new MemoryStream())
{
xmlSerializer.Serialize(xmlStream, m);
xmlStream.Position = 0;
xmlDoc.Load(xmlStream);
}
System.Diagnostics.Debug.WriteLine(xmlDoc.OuterXml);
}
}
The problem is that the output is not including the MyRequest details...
<?xml version="1.0"?>
<ArrayOfMyElement xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<MyElement>
<ID>1</ID>
</MyElement>
<MyElement>
<ID>2</ID>
</MyElement>
<MyElement>
<ID>3</ID>
</MyElement>
<MyElement>
<ID>4</ID>
</MyElement>
<MyElement>
<ID>5</ID>
</MyElement>
</ArrayOfMyElement>
XmlSerializer always ignores extra properties when serializing a collection; the only way to do it, as far as I know, is not to implement ICollection<MyElement> on your MyResults class, and instead expose the collection as a property:
public class MyResults
{
public MyRequest RequestDetails { get; set; }
public ICollection<MyElement> Items { get; set; }
}
(the Serializable attribute isn't needed for XML serialization)
Just change ICollection to Collection because XmlSerialization does not support Generic Interfaces:
public class MyResults
{
public MyResults()
{
this.Items= new Collection<MyElement>();
}
public MyRequest RequestDetails { get; set; }
public Collection<MyElement> Items { get; set; }
}

How to serialize container classes using custom serializer in C#

I need to serialize some container classes for which the HasValue property is evaluated to true.
I don´t need to remove invalid elements from the container List prior to the serialization. The serializer should be able to determine which objects need to be serialized or not. I guess a custom serializer can be suitable for my need but i don´t know how to figure out this. Any other solution / best practice would be appreciated.
Here my classes
public static class ContainerFactory
{
public static Container Create()
{
var container = new Container();
container.Persons.AddRange(new[]
{
new Person
{
FirstName = "Thomas"
},
new Person
{
FirstName = "Andrew",
LastName = "Martin",
Vehicles = new Vehicles
{
new Vehicle { Hsn = "65976GHR", Tsn = "HUZUKL"}
}
},
new Person
{
FirstName = "Arnold",
LastName = "Beckmann",
Vehicles = new Vehicles
{
new Vehicle { Hsn = "345XXXHZ"},
new Vehicle { Hsn = "659JUKI", Tsn = "787999HGF"}
}
}
});
return container;
}
}
[Serializable]
public class Container
{
public Container()
{
Persons = new Persons();
}
public Persons Persons { get; set; }
public void Serialize()
{
var serializer = new XmlSerializer(typeof (Container));
var streamWriter = new StreamWriter(#"C:\container.xml", false);
serializer.Serialize(streamWriter, this);
}
}
public class Persons: List<Person>
{
}
public class Vehicles: List<Vehicle>
{
public Vehicles()
{
}
public Vehicles(IEnumerable<Vehicle> vehicles):base(vehicles)
{
}
}
[Serializable]
public class Person : IHasValue
{
public Person()
{
this.Vehicles = new Vehicles();
this.Id = Guid.NewGuid().ToString();
}
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Vehicles Vehicles { get; set; }
public bool HasValue
{
get { return !string.IsNullOrEmpty(this.FirstName) && !string.IsNullOrEmpty(this.LastName); }
}
}
public interface IHasValue
{
bool HasValue { get;}
}
public class Vehicle: IHasValue
{
public string Hsn { get; set; }
public string Tsn { get; set; }
public bool HasValue
{
get { return !string.IsNullOrEmpty(Hsn) && !string.IsNullOrEmpty(Tsn); }
}
}
//Using the .NET XMLSerializer to test my container
Container container = ContainerFactory.Create();
container.Serialize();
Console.WriteLine("Press any Key to continue...");
Console.ReadLine();
Output
<?xml version="1.0" encoding="utf-8"?>
<Container xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Persons>
<Person>
<Id>76cdcc18-b256-40fe-b813-cd6c60e682ca</Id>
<FirstName>Thomas</FirstName>
<Vehicles />
</Person>
<Person>
<Id>26623bf9-d799-44d2-bc1a-7ec91292d1cd</Id>
<FirstName>Andrew</FirstName>
<LastName>Martin</LastName>
<Vehicles>
<Vehicle>
<Hsn>65976GHR</Hsn>
<Tsn>HUZUKL</Tsn>
</Vehicle>
</Vehicles>
</Person>
<Person>
<Id>f645cde1-10c8-4df5-81df-9b9db7712ec3</Id>
<FirstName>Arnold</FirstName>
<LastName>Beckmann</LastName>
<Vehicles>
<Vehicle>
<Hsn>345XXXHZ</Hsn>
</Vehicle>
<Vehicle>
<Hsn>659JUKI</Hsn>
<Tsn>787999HGF</Tsn>
</Vehicle>
</Vehicles>
</Person>
</Persons>
How can I achieve my goal to serialize Vehicles/Persons only for which HasValue == true?
you need to use:
ISerializable interface - every class that you want to serialize and every property you want in different node need to use the appropriate attribute. look at the example here
when you serialize a class check if HasValue == true
Persons : List < Person > should be also Serializable.
public void GetObjectData( SerializationInfo info, StreamingContext context )
{
foreach (Person person in this)
{
if(person.HasValue)
{
info.AddValue("Firsname", person, typeof(Person));
info.AddValue (....);
..............
}
}
}

Categories