difference between XmlSerializerOutputFormatter and XmlDataContractSerializerOutputFormatter - c#

I am wondering what is the difference between these two serializers. when setting accept header = application/xml. Am using Plain DTOs as return values, Which one is preferred? Also consumer of the api who request xml in response which should be used?
Am working on aspnet core web api 3.1, building restful apis. Any suggestions/redirects on the above query will be helpful.

The XmlSerializerOutputFormatter is an asp.net core outputformatter that uses the XmlSerializer internally, whereas the DataContractSerializerOutputFormatter uses the DataContractSerializer internally.
The DataContractSerializer is more flexible in configuration. For example it supports reference detection to prevent the serializer from recursively serializing items, which would normally cause an endless loop.
In my own projects, I prefer to use the DataContractSerializerOutputFormatter because it's able to cope with properties with private setter
public string Text { get; private set; }
Failing case
Dtos project
namespace DataContractSerializerPOC.Dtos
{
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
// Fullname can only be set from this project
public string FullName { get; internal set; }
}
public class PersonService
{
public List<Person> GetPeople()
{
// Create a list of people to be serialized
var people = new List<Person>
{
new Person { Id = 1, FirstName = "John", LastName = "Doe" },
new Person { Id = 2, FirstName = "Jim", LastName = "Flix" },
new Person { Id = 3, FirstName = "Jack", LastName = "Splick" },
};
// Set the fullname from this project
// In some cases you may need to do this, instead of implementing a readonly property
foreach (var person in people)
person.FullName = $"{person.FirstName} {person.LastName}";
return people;
}
}
}
Console project
namespace DataContractSerializerPOC
{
class Program
{
static void Main(string[] args)
{
var personService = new PersonService();
var people = personService.GetPeople();
var writer = new StringWriter();
var serializer = new XmlSerializer(typeof(List<Person>));
serializer.Serialize(writer, people);
}
}
}
Result
Working case with DataContractSerializer
Dtos project
namespace DataContractSerializerPOC.Dtos
{
[DataContract]
public class Person
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
// Fullname can only be set from this project
[DataMember]
public string FullName { get; internal set; }
}
public class PersonService
{
...
}
}
Console project
namespace DataContractSerializerPOC
{
class Program
{
static void Main(string[] args)
{
var personService = new PersonService();
var people = personService.GetPeople();
var memoryStream = new MemoryStream();
var serializer = new DataContractSerializer(typeof(List<Person>));
serializer.WriteObject(memoryStream, people);
memoryStream.Seek(0, SeekOrigin.Begin);
var text = new StreamReader(memoryStream).ReadToEnd();
}
}
}
Result
So the DataContractSerializer is able to deal with properties with a private setter, whilst the XmlSerializer isn't.

Related

C# Serializing does not add values to attributes in xml file

I am new to programming but I need to serialize data, I first read a csv and create classes based on the data in said csv. When I click on a button the List should be serialized for later use.
This is the code for both the Dealership (del) and the Car class:
[DataContract]
public class Dealership
{
[DataMember]
private List<Car> carList = new List<Car>();
public void ImportCars()
{
carList.Clear();
var fileContent = string.Empty;
var filePath = string.Empty;
using (OpenFileDialog openFileDialog = new OpenFileDialog())
{
openFileDialog.InitialDirectory = "c:\\";
openFileDialog.Filter = "csv files (*.csv)|*.csv";
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
filePath = openFileDialog.FileName;
using (var reader = new StreamReader(filePath))
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
csv.Read();
csv.ReadHeader();
while (csv.Read())
{
var record = new Car
{
Model = csv.GetField("model"),
Brand = csv.GetField("brand"),
Year = Convert.ToInt32(csv.GetField("year")),
Price = Convert.ToDecimal(csv.GetField("price"))
};
carList.Add(record);
}
}
}
}
}
}
[DataContract]
public class Car
{
[DataMember]
private string brand;
[DataMember]
private string model;
[DataMember]
private int year;
[DataMember]
private decimal price;
public string Brand { get; set; }
public string Model { get; set; }
public int Year { get; set; }
public decimal Price { get; set; }
}
This is the form code:
Dealership del1 = del;
FileStream? fs = null;
try
{
fs = new FileStream("dealership.xml", FileMode.OpenOrCreate, FileAccess.Write);
Type mainType = typeof(Dealership);
//List<Type> auxiliryTypes = new List<Type>() { typeof(Car), typeof(Customer) };
DataContractSerializer dcs = new DataContractSerializer(mainType);
dcs.WriteObject(fs, del1);
}
finally
{
if (fs!=null) fs.Close();
}
Snippet of XML output:
<Dealership xmlns="http://schemas.datacontract.org/2004/07/Individual_Assignment" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><carList><Car><brand i:nil="true"/><model i:nil="true"/><price>0</price><year>0</year></Car>
How is it possible for the XML to not contain any values for the cars, when I sell a car by typing customer details in the form it does serialize those values but nothing else.
As a rule of thumb, make your Data Transfer Object (DTO) classes (i.e. classes intended for serialization) have only public properties. You should probably also be using generic collections:
[DataContract]
public class Dealership {
[DataMember]
public List<Car> CarList {get;set;} = new List<Car>();
}
Using public properties tend to work with most serialization libraries. There can be exceptions, some can handle private properties, some can handle immutable types, but then you need to check what specific syntax your library support. I do not remember the exact rules for DataContractSerializer.
Also note that your should probably separate your DTO classes from your domain classes, so you do not have to expose internal fields when actually using your objects, only when communicating with some other part of the system.
I would also advice setting up a unit test to serialize and deserialize your classes. That is an easy way to see what works and what does not. It can also be used to check that old versions of your classes still de serialize correctly if that is a concern for you.
Full example:
[DataContract]
public class Car
{
[DataMember]
public string Brand { get; set; }
[DataMember]
public string Model { get; set; }
[DataMember]
public int Year { get; set; }
[DataMember]
public decimal Price { get; set; }
}
[DataContract]
public class Dealership
{
[DataMember]
public List<Car> Cars { get; set; } = new ();
};
[Test]
public void ShouldSerializeAndDeserialize()
{
var sut = new DataContractXml();
var car = new Car(){Brand = "Volvo", Model = "v70", Price = 20000, Year = 2004};
var dealer = new Dealership();
dealer.Cars.Add(car);
using var ms = new MemoryStream();
var serializer = new DataContractSerializer(typeof(Dealership));
serializer.WriteObject(ms, dealer);
ms.Position = 0;
var result = (Dealership)serializer.ReadObject(ms);
Console.WriteLine(result.Cars[0].Brand);
}

Polymorphism not working on collections elements using MessagePack-Csharp nuget

I'm not being able to deserialize a collection of elements where the instances have a Inheritance relationship between them.
Does anyone came across this issue?
So my use case is this:
My model is similiar to this:
[DataContract]
public class Item
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public bool Valid { get; set; }
}
[DataContract]
public class IntermediateItem : Item
{
[DataMember]
public int Priority { get; set; }
}
[DataContract]
public class ExtendedItem : IntermediateItem
{
[DataMember]
public int Count { get; set; }
[DataMember]
public ItemsCollection Childs { get; set; }
}
And Items Collection is something like this:
[DataContract]
public class ItemsCollection : Collection<Item>
{
}
The setup that I have made to ensure the proper deserialization is:
Defining the CollectionFormatterBase:
public class ItemCollectionFormatterBase : CollectionFormatterBase<Item, ItemsCollection>
{
protected override ItemsCollection Create(int count)
{
return new ItemsCollection();
}
protected override void Add(ItemsCollection collection, int index, Item value)
{
collection.Add(value);
}
}
The example that is not working, and not working I mean, the deserialized instances are all of base type, some how the inheritance relationship got lost in the serialization.
Example:
MessagePack.Resolvers.CompositeResolver.RegisterAndSetAsDefault(new[] { new ItemCollectionFormatterBase() }, new[] { StandardResolver.Instance });
ExtendedItem instance = new ExtendedItem()
{
Id = 1,
Name = "Extended Item",
Priority = 121,
Valid = true,
Count = 10,
Childs = new ItemsCollection(new List<Item>() { new Item() { Id = 1 }, new IntermediateItem() { Priority = 10 }, new ExtendedItem() { Count = 10 } })
};
byte[] bytes = MessagePackSerializer.Serialize(instance);
using (FileStream file = new FileStream(this.filePath.AbsolutePath, FileMode.Create))
{
await file.WriteAsync(bytes , 0, payload.Length);
await file.FlushAsync();
}
using (FileStream file = new FileStream(testsFolder + #"\ExtendedItem.msgPack-csharp.dat", FileMode.Open))
{
file.Seek(0, SeekOrigin.Begin);
deserializedInstance = MessagePackSerializer.Deserialize<ExtendedItem>(file);
}
looking at the deserializedInstance Childs elements they all are from Item Type.
Can you tell me what I'm doing wrong ? What is missing ?
A small update regarding Item definition:
[DataContract]
[KnownType(typeof(IntermediateItem))]
[KnownType(typeof(ExtendedItem))]
public class Item
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public bool Valid { get; set; }
}
This also does not work. :(
Well looks like MessagePackSerializer static type as a static inner class called Typeless and that solve my problem:
With a instance of a ExtendedItem:
ExtendedItem instance = new ExtendedItem()
{
Id = 1,
Name = "Extended Item",
Priority = 121,
Valid = true,
Count = 10,
Childs = new ItemsCollection(new List<Item>() { new Item() { Id = 1 }, new IntermediateItem() { Priority = 10 }, new ExtendedItem() { Count = 10 } })
};
I was able to serialize that and deserialize with success !
byte[] bytes = MessagePackSerializer.Typeless.Serialize(instance);
await fileManager.WriteAsync(bytes);
ExtendedItem deserializedInstance = null;
deserializedInstance = MessagePackSerializer.Typeless.Deserialize(bytes) as ExtendedItem;
despite the serialization and deserialization worked on .NET this sample did not work when deserializing in nodejs with msgpackjs package.

XML serialization override dervied class name with XSI:Type

I am using following code for Student and Employee class derived from Person.
[XmlInclude(typeof(Student)), XmlInclude(typeof(Employee))]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Gender { get; set; }
}
[XmlType("Student")]
public class Student : Person
{
public int StudentId { get; set; }
public List<string> Subjects { get; set; }
}
[XmlType("Employee")]
public class Employee : Person
{
public int EmployeeId { get; set; }
public float Experience { get; set; }
}
I am creating a list of Person classes and initiate with following values and generating XML by serializing the same.
public class TestSerialization
{
List<Person> lstPerson = new List<Person>();
public void XMLGen()
{
Person obj = new Person() { Name = "Person1", Age = 10, Gender = "M" };
Person obj2 = new Person() { Name = "Person2", Age = 10, Gender = "F" };
Student objS = new Student()
{
Name = "Student1",
Age = 20,
Gender = "M",
StudentId = 1,
Subjects = new List<string>() { "Math", "Science" }
};
Student objS2 = new Student()
{
Name = "Student2",
Age = 15,
Gender = "F",
StudentId = 1,
Subjects = new List<string>() { "Physics", "Chemistry" }
};
Employee objE = new Employee()
{
Name = "Employee1",
Age = 15,
Gender = "F",
EmployeeId = 1,
Experience = 5.5f
};
Employee objE2 = new Employee()
{
Name = "Employee2",
Age = 15,
Gender = "M",
EmployeeId = 2,
Experience = 6.5f
};
lstPerson.Add(obj);
lstPerson.Add(obj2);
lstPerson.Add(objS);
lstPerson.Add(objS2);
lstPerson.Add(objE);
lstPerson.Add(objE2);
Type[] types = { typeof(Student), typeof(Employee) };
XmlSerializer objXml = new XmlSerializer(typeof(List<Person>), types);
using (StringWriter textWriter = new StringWriter())
{
objXml.Serialize(textWriter, lstPerson);
string aa = textWriter.ToString();
}
}
}
But XML that generated contains derived class name as xsi:type="Student" and xsi:type="Employee" as shown below.
<Person xsi:type="Student">
<Name>Student1</Name>
<Age>20</Age>
<Gender>M</Gender>
<StudentId>1</StudentId>
<Subjects>
<string>Math</string>
<string>Science</string>
</Subjects>
and for Employee it is
<Person xsi:type="Employee">
<Name>Employee2</Name>
<Age>15</Age>
<Gender>M</Gender>
<EmployeeId>2</EmployeeId>
<Experience>6.5</Experience>
Is it possible the we get XML node name as Student,Employee rather than Person with xsi:type?
I want XML should be like this.
<Employee>
<Name>Employee2</Name>
<Age>15</Age>
<Gender>M</Gender>
<EmployeeId>2</EmployeeId>
<Experience>6.5</Experience>
The xsi:type is needed for deserialization, when you try to read from the XML again. Otherwise the deserializer wouldn't know what class to deserialize to.
If you are not planning to deserialize into a class again, then you could add your own custom serializer.
Adding xmlroot should give the root tag as employee. You cannot eliminate the type attribute because of the inheritance in the class structure.
[XmlInclude(typeof(Student)), XmlInclude(typeof(Employee))]
[XmlRoot("Employee")]
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Gender { get; set; }
}
[XmlType("Student")]
public class Student : Person
{
public int StudentId { get; set; }
public List<string> Subjects { get; set; }
}
[XmlType("Employee")]
public class Employee : Person
{
public int EmployeeId { get; set; }
public float Experience { get; set; }
}
I got the solution from https://msdn.microsoft.com/en-us/library/3z3z5s6h%28v=vs.110%29.aspx link. Use XmlElementAttribute to change element name and add the XmlElementAttribute to a XmlAttributes instance. Then add the XmlAttributes to a XmlAttributeOverrides instance, specifying the type being overridden and the name of the member that accepts the derived class.
public class Orders
{
public List<Person> Persons;
}
public void SerializeObject(string filename)
{
// Each overridden field, property, or type requires
// an XmlAttributes instance.
XmlAttributes attrs = new XmlAttributes();
// Creates an XmlElementAttribute instance to override the
// field that returns Book objects. The overridden field
// returns Expanded objects instead.
XmlElementAttribute attr = new XmlElementAttribute();
attr.ElementName = "Student";
attr.Type = typeof(Student);
XmlElementAttribute attrE = new XmlElementAttribute();
attrE.ElementName = "Employee";
attrE.Type = typeof(Employee);
// Adds the element to the collection of elements.
attrs.XmlElements.Add(attr);
attrs.XmlElements.Add(attrE);
// Creates the XmlAttributeOverrides instance.
XmlAttributeOverrides attrOverrides = new XmlAttributeOverrides();
// Adds the type of the class that contains the overridden
// member, as well as the XmlAttributes instance to override it
// with, to the XmlAttributeOverrides.
attrOverrides.Add(typeof(Orders), "Persons", attrs);
// Creates the XmlSerializer using the XmlAttributeOverrides.
XmlSerializer s =
new XmlSerializer(typeof(Orders), attrOverrides);
During serialization, it will use override attributes to overrride their properties.

Cannot initialize type 'PeopleModel' with a collection initializer because it does not implement 'System.Collections.IEnumerable'

I have a method that aims to fill a PersonModel from OleDB;
public IEnumerable<PeopleModel> GetPeopleDetails()
{
var constr = ConfigurationManager.ConnectionStrings["dbfString"].ConnectionString;
using (var dbfCon = new OleDbConnection(constr))
{
dbfCon.Open();
using (var dbfCmd = dbfCon.CreateCommand())
{
dbfCmd.CommandText = "SELECT pp_firstname, pp_surname, pp_title, pp_compnm, pp_hmaddr1, pp_hmaddr2, pp_hmtown, pp_hmcounty, pp_hmpcode, pp_spouse, pp_children FROM people ORDERBY pp_surname";
using (var myReader = dbfCmd.ExecuteReader())
{
var peopleList = new List<PeopleModel>();
while (myReader.Read())
{
var details = new PeopleModel
{
details.Firstname = myReader[0].ToString(),
details.Lastname = myReader[1].ToString(),
details.Title = myReader[2].ToString(),
details.Company = myReader[3].ToString(),
details.Addr1 = myReader[4].ToString(),
details.Addr2 = myReader[5].ToString(),
details.Town = myReader[6].ToString(),
details.County = myReader[7].ToString(),
details.Spouse = myReader[8].ToString(),
details.Children = myReader[9].ToString(),
};
peopleList.Add(details);
}
return peopleList;
}
}
}
}
This code is pretty much identical to the method I am using to fill a companies details, which works no problem. Here is the PeopleModel I am using to build a person.
namespace SdcDatabase.Model
{
public class PeopleModel
{
public string Firstname { get; set; }
public string Lastname { get; set; }
public string Title { get; set; }
public string Company { get; set; }
public string Addr1 { get; set; }
public string Addr2 { get; set; }
public string Town { get; set; }
public string County { get; set; }
public string Spouse { get; set; }
public string Children { get; set; }
}
}
Although the companies method has worked fine previously, I am now getting the following error when I try to build my project after implementing the People code: Cannot initialize type 'PeopleModel' with a collection initializer because it does not implement 'System.Collections.IEnumerable'
I really am at a lost cause with this as it is working in an almost identical method for a Company.
Correct syntax, without details. in the assignments in the initializer:
var details = new PeopleModel
{
Firstname = myReader[0].ToString(),
Lastname = myReader[1].ToString(),
Title = myReader[2].ToString(),
Company = myReader[3].ToString(),
Addr1 = myReader[4].ToString(),
Addr2 = myReader[5].ToString(),
Town = myReader[6].ToString(),
County = myReader[7].ToString(),
Spouse = myReader[8].ToString(),
Children = myReader[9].ToString(),
};

How to not serialize an object based on a property's value?

If the tags didn't give it away, I'm working with C#'s XmlSerializer class.
Say, for example, I have a Person class with various properties including age (int), name (string), and deceased (bool). Is there a way to specify that I don't want to serialize any objects whose deceased flags are true?
Edit: I should have specified, but unfortunately due to the situation I can't really edit my list of objects because it's a member of another class, which is what I'm actually serializing. Are there any other suggestions?
Assuming that you have following type of Class structure(As you specified in the comment)
public class Person
{
public int Age { get; set; }
public string Name { get; set; }
public bool Deceased { get; set; }
}
public class Being
{
public string Data { get; set; }
[XmlElement("Human")]
public Person Human { get; set; }
public bool ShouldSerializeHuman()
{
return !this.Human.Deceased;
}
}
Here I have added a method called ShouldSerialize this is called a pattern for XML serialization. Here you can use XmlArray and XmlArrayItem for lists etc.(With given name) then the ShouldSerialize checks if it can be serialized.
Below is the code I used for testing.
private static void Main(string[] args)
{
var livingHuman = new Person() { Age = 1, Name = "John Doe", Deceased = true };
var deadHuman = new Person() { Age = 1, Name = "John Doe", Deceased = false };
XmlSerializer serializer = new XmlSerializer(typeof(Being));
serializer.Serialize(Console.Out, new Being { Human = livingHuman, Data = "new" });
serializer.Serialize(Console.Out, new Being { Human = deadHuman, Data = "old" });
}
And here's the output:
=============================
Update:
If you have list of Person as Humans:
public class Being
{
// [XmlAttribute]
public string Data { get; set; }
// Here add the following attributes to the property
[XmlArray("Humans")]
[XmlArrayItem("Human")]
public List<Person> Humans { get; set; }
public bool ShouldSerializeHumans()
{
this.Humans = this.Humans.Where(x => !x.Deceased).ToList();
return true;
}
}
Sample Test:
private static void Main(string[] args)
{
var livingHuman = new Person() { Age = 1, Name = "John Doe", Deceased = true };
var deadHuman = new Person() { Age = 1, Name = "John Doe", Deceased = false };
var humans = new List<Person> { livingHuman, deadHuman };
XmlSerializer serializer = new XmlSerializer(typeof(Being));
serializer.Serialize(Console.Out, new Being() { Humans = humans, Data = "some other data" });
}
Output:
If you have a list of Person objects and only want to serialise some of them, then just filter out the ones you don't need. For example:
List<Person> people = GetPeople(); //from somewhere
List<Person> filteredPeople = people.Where(p => !p.Deceased);
Now you only need to serialise filteredPeople.

Categories