Deserialization of serialized data fails - c#

I am trying to deserialize an XML document that I am also serializing at another time. I am using it to store a configuration file.
This is my Code:
namespace OrderTracker
{
[Serializable]
public class AutofillValues
{
private string fileName = Directory.GetCurrentDirectory() + "\\bin\\settings.db";
public ComboBox.ObjectCollection Vendors { get; set; }
public ComboBox.ObjectCollection Products { get; set; }
public ComboBox.ObjectCollection Companies { get; set; }
public void save(AutofillValues afv)
{
if (!File.Exists(fileName))
{
FileStream fs = File.Create(fileName);
fs.Close();
}
XmlSerializer x = new XmlSerializer(typeof(AutofillValues));
TextWriter writer = new StreamWriter(fileName);
x.Serialize(writer, afv);
writer.Close();
}
public AutofillValues load()
{
XmlSerializer x = new XmlSerializer(typeof(AutofillValues));
TextReader file = new StreamReader(fileName);
AutofillValues av = (AutofillValues)x.Deserialize(file);
file.Close();
return av;
}
}
}
The error message that I am getting when trying to deserialize the file is this;
An unhandled exception of type 'System.InvalidOperationException' occurred in System.Xml.dll
Additional information: There is an error in XML document (2, 2).*
This is the XML document:
<?xml version="1.0" encoding="utf-8"?>
<AutofillValues xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Vendors>
<anyType xsi:type="xsd:string">Test Vendor</anyType>
</Vendors>
<Products>
<anyType xsi:type="xsd:string">Test Product</anyType>
</Products>
<Companies>
<anyType xsi:type="xsd:string">Test Company</anyType>
</Companies>
</AutofillValues>
How can I deserialize the XML file and get back the serialized data?

I just changed this part and it worked for me.

You can not deserialize the XML back, because the class ComboBox.ObjectCollection does not have a standard (parameterless) constructor. This is a limitation of the XmlSerializer class, as stated in this SO post.
There is however another problem with your current code - even if the deserialization somehow works, than you still need to assign the collection to a ComboBox control, which the deserializer still can't do.
Instead of using the ComboBox.ObjectCollection class to store the items, I would suggest using either an array or a list of objects (as #kenlacoste suggested). Such collections can be easily inserted into the ComboBox using
the comboBox.Items.AddRange(arrayOfObjects) method.
Another refactoring would be to extract the serialization logic of the data class. Currently it is confusing to save and load the data, because I presume you want to save/fill the caller object:
save: object.save(object); - you can use the this keyword in the save method
load: object = object.load(); - same here, there is no need to return the value, use the this keyword to fill the existing properties
The changed code:
public class AutofillValues
{
private string fileName = #"d:\settings.db";
public object[] Vendors { get; set; }
public object[] Products { get; set; }
public object[] Companies { get; set; }
public void save()
{
XmlSerializer x = new XmlSerializer(typeof(AutofillValues));
// with using there is no need to close the writer explicitely
// second parameter - file is created if it does not exist
using (var writer = new StreamWriter(fileName, false))
{
x.Serialize(writer, this);
}
}
public void load()
{
XmlSerializer x = new XmlSerializer(typeof(AutofillValues));
AutofillValues av = (AutofillValues)x.Deserialize(new StreamReader(fileName));
this.Companies = av.Companies;
this.Vendors = av.Vendors;
this.Products = av.Products;
}
}
IMO the modified code is easier to read and understand:
var afv = new AutofillValues();
afv.load();
//use avf.Products
// or afv.save();
I would also suggest to extract the data that needs to be saved in an extra class, for example:
[Serializable]
public class AutofillValuesData
{
public Object[] Vendors { get; set; }
public Object[] Products { get; set; }
public Object[] Companies { get; set; }
}
In the class AutofillValues remove the three properties and leave just one:
public AutofillValuesData Data { get; set; }
Then the logic can be modified to fill the ComboBox controls from the filled data object. This way your data will not be hardwired to the UI and this would make the code more maintainable. You can use a helper like AutoMapper to remove the repetitive code (like mappig objA.Vendors to objB.Vendors).

Related

XML serialisation for class properties with additional meta data

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?
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
}
}
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 XmlAttributeAttribute on the generic class' metadata, so it is written as an XML attribute on the parent property
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)
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
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.

Modify data during Serialization - C# - Json.Net

I have one seializable class in C# as below
namespace DataRequest
{
[Serializable]
public class BaseData
{
[JsonProperty(PropertyName = "u_additional_info")]
public string DeskNumber { get; set; }
}
}
I am fetching data from Database and mapping to this class.I have requirement
DeskNumber will contain data in following format
Format1.
<AdditionalInfo><Number>164</Number></AdditionalInfo>
Format2
AdditionalInfo><Code>GLW51</Code><Lang>GLW51</LangCode><TzCode>GLW51</TzCode></AdditionalInfo>
During serialization if data is 1st fromat i need to return value under tag (ie .164).Value under Number tag will be different at different time.So need a logic find value under Number tag .If data is in any other format other than "Format1" then return it as blank .
Or
I am okay on applying ths logic even if its during setting value rather than during serialisation
Can anyone help on this
if Format1 and Format2 are same file contents coming in at different
times you can be spesitifcly serialized XmlContent this code
Try this
// Generic DeSerialization metod.
public T DeSerialization<T>(string xmlStrig) where T : class
{
XmlSerializer xmlSerializer = new XmlSerializer(typeof(T));
using (StringReader sReader = new StringReader(xmlStrig))
{
return (T)xmlSerializer.Deserialize(sReader);
}
}
// Accepted class
[Serializable]
public class AdditionalInfo
{
[XmlElement]
public string Number { get; set; }
}
// DeSerialize to Object code
// if you have Xml-string you can send parameter XmlString directly
// string xmlString = System.IO.File.ReadAllText(#"..\\XMLFile1.xml");
AdditionalInfo ast = DeSerialization<AdditionalInfo>(yourXmlString);
You can check www.bilisim.io for detailed information
Use the Following code, You will get Value under the Number Tag. resultingMessage.Number contains the required result
string name = YourXmlNodes;
XmlSerializer serializer = new XmlSerializer(typeof(AdditionalInfo));
MemoryStream memStream = new MemoryStream(Encoding.UTF8.GetBytes(name));
AdditionalInfo resultingMessage= (AdditionalInfo)serializer.Deserialize(memStream);
namespace ConsoleApplication1
{
[XmlRoot(ElementName = "AdditionalInfo")]
public class AdditionalInfo
{
[XmlElement(ElementName = "Number")]
public string Number { get; set; }
}
}

Deserialization XML to object with list in c#

I want to deserialize XML to object in C#, object has one string property and list of other objects.
There are classes which describe XML object, my code doesn't work (it is below, XML is at end of my post). My Deserialize code doesn't return any object.
I think I do something wrong with attributes, could you check it and give me some advice to fix it.
Thanks for your help.
[XmlRoot("shepherd")]
public class Shepherd
{
[XmlElement("name")]
public string Name { get; set; }
[XmlArray(ElementName = "sheeps", IsNullable = true)]
[XmlArrayItem(ElementName = "sheep")]
public List<Sheep> Sheeps { get; set; }
}
public class Sheep
{
[XmlElement("colour")]
public string colour { get; set; }
}
There is C# code to deserialize XML to objects
var rootNode = new XmlRootAttribute();
rootNode.ElementName = "createShepherdRequest";
rootNode.Namespace = "http://www.sheeps.pl/webapi/1_0";
rootNode.IsNullable = true;
Type deserializeType = typeof(Shepherd[]);
var serializer = new XmlSerializer(deserializeType, rootNode);
using (Stream xmlStream = new MemoryStream())
{
doc.Save(xmlStream);
var result = serializer.Deserialize(xmlStream);
return result as Shepherd[];
}
There is XML example which I want to deserialize
<?xml version="1.0" encoding="utf-8"?>
<createShepherdRequest xmlns="http://www.sheeps.pl/webapi/1_0">
<shepherd>
<name>name1</name>
<sheeps>
<sheep>
<colour>colour1</colour>
</sheep>
<sheep>
<colour>colour2</colour>
</sheep>
<sheep>
<colour>colour3</colour>
</sheep>
</sheeps>
</shepherd>
</createShepherdRequest>
XmlRootAttribute does not change the name of the tag when used as an item. The serializer expects <Shepherd>, but finds <shepherd> instead. (XmlAttributeOverrides does not seem to work on arrays either.) One way to to fix it, is by changing the case of the class-name itself:
public class shepherd
{
// ...
}
An easier alternative to juggling with attributes, is to create a proper wrapper class:
[XmlRoot("createShepherdRequest", Namespace = "http://www.sheeps.pl/webapi/1_0")]
public class CreateShepherdRequest
{
[XmlElement("shepherd")]
public Shepherd Shepherd { get; set; }
}

Error while serializing a class to XML

I am serializing Linq object to using XmlSerializer but I am getting
"There was an error reflecting type
'System.Collections.Generic.List`1"
I have extended the Linq object with Serilizable attributes, something that m I doing incorrect here?
[Serializable]
public partial class Customer
{
}
[Serializable]
public partial class Comment
{
}
[Serializable]
public class CustomerArchive
{
public Customer MyCustomer{ get; set; }
public Comment MyComment { get; set; }
}
internal static void Serialize(IEnumerable<CustomerArchive> archive, string fileName)
{
var serializer = new XmlSerializer(typeof(List<CustomerArchive>)); // Error
using (var stream = File.Create(fileName))
{
using (var sw = new StreamWriter(stream))
using (var writer = new XmlTextWriter(sw)
{
Indentation = 5,
Formatting = Formatting.Indented
})
serializer.Serialize(writer, new List<CustomerArchive>(archive));
}
}
I think you have some properties inside MyCustomer class that cannot be serialized by the XML Serializer. Like a generic Dictionary<> for example.
Put [XmlIgnore] attribute on those properties and try again. If after that your code works you will simply need to make some surrogate properties that would serialize to XML and back without any issues.

Can I serialize Xml for an MVC Web API without an object type?

So I'm writing my first mvc page, and I'm trying to write a series of routes to allow a reporting system to create simple reports. The xml is small, here is an example:
<xml><root><item><value>23</value></item></root>
I tried this:
using (StringWriter xmlStringWriter = new StringWriter())
{
using (XmlWriter xmlWriter = XmlWriter.Create(xmlStringWriter))
{
XmlWriter.WriteStartElement("root")
...
}
return xmlStringWriter.ToString();
}
but this obviously returns a string and is not interpreted as xml by the browser. I also know* that if you return an object that is serializable then the browser knows to interpret that as xml or json. So I tried defining a set of objects to hold each other in the way the xml is nested:
[Serializable]
public class XmlReportRoot
{
[System.Xml.Serialization.XmlAttribute("root")]
public List<XmlReportItem> item { get; set; }
}
[Serializable]
public class XmlReportItem
{
[System.Xml.Serialization.XmlAttribute("item")]
public XmlReportValue value { get; set; }
}
[Serializable]
public class XmlReportValue
{
[System.Xml.Serialization.XmlAttribute("value")]
public string count { get; set; }
}
and:
XmlReportRoot xmlRoot = new XmlReportRoot();
XmlReportItem xmlItem = new XmlReportItem();
List<XmlReportItem> itemList = new List<XmlReportItem>();
itemList.Add(xmlItem);
XmlReportValue xmlValue = new XmlReportValue();
xmlValue.count = newCustomers.ToString();
xmlItem.value = xmlValue;
xmlRoot.item = itemList;
XmlSerializer xmlSer = new XmlSerializer(typeof(XmlReportRoot));
xmlSer.Serialize(xmlRoot); //this line doesn't work
but this just feels wrong, and I couldn't quite get the serialization to work without worrying about a file stream, which I would rather do.
So I guess I was trying to find a way to do something like XmlWriter but be able to serialize that without an object type and return that, instead of having to worry about custom serializable objects types.
Use XmlWriter.Create(Response.OutputStream) and Response.ContentType = "application/xml"

Categories

Resources