XmlSerializer exception with large amount of objects - c#

I am storing DLL information into the following class:
[XmlRoot("Modules")]
[XmlInclude(typeof(Module))]
public class Modules : List<Module> {}
[XmlType("Module")]
public class Module
{
[XmlIgnore()]
public bool Selected { get; set; }
[XmlElement()]
public string Name { get; set; }
[XmlElement()]
public string Version { get; set; }
[XmlElement()]
public byte[] Binary;
}
When I serialize a handful of Module classes in the list using the following code, it is fine.
XmlSerializer serializer = new XmlSerializer(typeof(Modules));
using (StringWriter sw = new StringWriter())
{
serializer.Serialize(sw, ugModules.DataSource);
}
However, my problem comes when within the list, I have 113 items - now obviously that is quite a lot of data with the binary.
I am getting a System.OutOfMemory exception, I seem to remember having to increase a threshold of some sort but I can't find the correct property or method for what exactly I have to change.
I am serializing this to XML to pass the XML into SQL and then store the information within a table, just for a bit of scope on what exactly I am doing.
How do I get around the exception? Is there a better way to do this?

Related

Serializing ArrayList outputs ArrayOfAnyType

I do have a problem with serializing a ArrayList. Most propably I use wrong XML Attributes for it since I when I changed them it would not even serialize it and got errors like 'The type may not be used in this context.'
I need to use a non generic ArrayList. On adding [XmlArray("LineDetails")] made this code to run but the output is not correct, it should give me the LineDetails structure. Any idea how to fix this?
This is a part of a whole xml like Document > Header > LineCollection > LineDeatails.
The problem is only with this details if I use a standard string field it is ok but the range of the colletion if changing with every document.
[XmlType(TypeName = "LineCollection")]
public class LineCollection
{
public String LineCount{ get; set; }
// [XmlElement(ElementName = "LineDetails")]
[XmlArray("LineDetails")]
public ArrayList LineDetails{ get; set; }
}
public class LineDetails: ArrayList
{
public String LineNum{ get; set; }
public String ItemId{ get; set; }
public String ItemName{ get; set; }
//... only strings
}
public class Utf8StringWriter : StringWriter
{
public override Encoding Encoding => new UTF8Encoding(false);
}
public string Serialize()
{
// var xmlserializer = new XmlSerializer(this.GetType());
var xmlserializer = new XmlSerializer(this.GetType(), new Type[] { typeof(LineDetails) });
var Utf8StringWriter = new Utf8StringWriter();
var xns = new XmlSerializerNamespaces();
xns.Add(string.Empty, string.Empty);
using (var writer = XmlWriter.Create(Utf8StringWriter))
{
xmlserializer.Serialize(writer, this, xns);
return Utf8StringWriter.ToString();
}
}
And the incorrect output of this...
<LineColletion>
<LineDetails>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="ArrayOfAnyType"/>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="ArrayOfAnyType"/>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="ArrayOfAnyType"/>
</LineDetails>
</LineColletion>
it should be like this
<LineColletion>
<LineDetails>
<LineNum>1</LineNum>
<ItemId>Item_2321</ItemId>
<ItemName>TheItemName</ItemName>
</LineDetails>
<LineDetails>
<LineNum>2</LineNum>
<ItemId>Item_232100000</ItemId>
<ItemName>TheItemName0</ItemName>
</LineDetails>
<LineDetails>
<LineNum>3</LineNum>
<ItemId>Item_23217777</ItemId>
<ItemName>TheItemName7</ItemName>
</LineDetails>
</LineColletion>
Now the wrong xml looks like this...
<LineDetails>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="LineDetails">
<LineNum>1</LineNum>
<ItemId>Item_2321</ItemId>
<ItemName>TheItemName</ItemName>
</anyType>
<anyType xmlns:p5="http://www.w3.org/2001/XMLSchema-instance" p5:type="LineDetails">
<LineNum>2</LineNum>
<ItemId>Item_2321</ItemId>
<ItemName>TheItemName</ItemName>
</anyType>
</LineDetails>
You may generate the required XML by modifying your data model as follows:
[XmlType(TypeName = "LineColletion")] // Fixed: TypeName. But do you want LineColletion (misspelled) or LineCollection (correctly spelled)? Your XML shows LineColletion but your code used LineCollection.
public class LineCollection
{
public String LineCount{ get; set; }
[XmlElement("LineDetails", typeof(LineDetails))] // Fixed -- specify type(s) of items in the ArrayList.
public ArrayList LineDetails{ get; set; }
}
public class LineDetails // Fixed: removed inheritance from ArrayList.
{
public String LineNum{ get; set; }
public String ItemId{ get; set; }
public String ItemName{ get; set; }
//... only strings
}
Notes:
Your model makes LineDetails inherit from ArrayList. XmlSerializer will never serialize collection properties, it will only serialize collection items. In order to serialize it correctly, I removed the inheritance since you don't seem to be using it anyway.
If you really need LineDetails to inherit from ArrayList, you will need to implement IXmlSerializable or replace it collection with a DTO.
Implementing IXmlSerializable is tedious and error-prone. I don't recommend it.
Your LineDetails collection is serialized without an outer wrapper element. To make the serializer do this, apply XmlElementAttribute to the property.
ArrayList is an untyped collection, so you must inform XmlSerializer of the possible types it might contain. You have two options:
Assigning a specific type to a specific element name by setting XmlElementAttribute.Type (or XmlArrayItemAttribute.Type), OR
Adding xsi:type attributes by informing the serializer of additional included types. You are doing this by passing them into the constructor, which is why you are seeing the p5:type="LineDetails" attribute.
Since you don't want the attributes, you need to set the element name by setting the type like so:
[XmlElement("LineDetails", typeof(LineDetails))]
The XML element corresponding to your LineCollection is named <LineColletion>. Note that the spelling is inconsistent. You will need to set [XmlType(TypeName = "LineColletion")] to the name you actually want.
Demo fiddle here.

Deserialization of serialized data fails

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).

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"

How to pass multidimensional list or tuple to C# Web Service

I'm working on a web service that needs to accept a collection with three values of different types. The values are
SkuNumber (integer),
FirstName (string),
LastName (string)
I want the web service to accept a list of 100 instances of these values but am not sure how to go about it. Do I use a multidimensional list or array? Maybe a tuple? Or can I just create a simple class structure and accept a list of that class?
This is all simple enough in a normal application, I'm just not sure how the app calling the web service would pass the data with any of the given options.
Can someone give me some pointers?
If a shared assembly is not feasible, you can always go with good ol' XML. It may not be the optimal solution and I'm sure plenty of users here will balk at the idea, but it is easy to support and relatively quick to implement, so it really depends on your individual situation and the skill level of the developers responsible for supporting the application.
The benefit to using XML here, is that the calling application can be written in almost any language on almost any platform, as long as it adheres to the XML structure.
The XML string should be easy enough to generate in the calling application, but the biggest downside here is that if you have a ton of data, the processing may take longer than desired -- on both ends of the service.
Here is a working sample if you want to give it a try:
public class Whatever
{
public int SkuNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
[WebMethod]
public void HelloWorld(string xmlString)
{
//make all the node names + attribute names lowercase, to account for erroneous xml formatting -- leave the values alone though
xmlString = Regex.Replace(xmlString, #"<[^<>]+>", m => m.Value.ToLower(),RegexOptions.Multiline | RegexOptions.Singleline);
var xmlDoc = LoadXmlDocument(xmlString);
var listOfStuff = new List<Whatever>();
var rootNode = xmlDoc.DocumentElement;
foreach(XmlNode xmlNode in rootNode)
{
var whatever = new Whatever
{
FirstName = xmlNode["first_name"].InnerText,
LastName = xmlNode["last_name"].InnerText,
SkuNumber = Convert.ToInt32(xmlNode["sku_number"].InnerText)
};
listOfStuff.Add(whatever);
}
}
public static XmlDocument LoadXmlDocument(string xmlString)
{
//some extra stuff to account for URLEncoded strings, if necessary
if (xmlString.IndexOf("%3e%") > -1)
xmlString = HttpUtility.UrlDecode(xmlString);
xmlString = xmlString.Replace((char)34, '\'').Replace("&", "&").Replace("\\", "");
var xmlDocument = new XmlDocument();
xmlDocument.PreserveWhitespace = false;
xmlDocument.LoadXml(xmlString);
return xmlDocument;
}
Your XML would look like this:
<stuff_to_track>
<whatever>
<sku_number>1</sku_number>
<first_name>jimi</first_name>
<last_name>hendrix</last_name>
</whatever>
<whatever>
<sku_number>2</sku_number>
<first_name>miles</first_name>
<last_name>davis</last_name>
</whatever>
<whatever>
<sku_number>3</sku_number>
<first_name>david</first_name>
<last_name>sanborn</last_name>
</whatever>
<whatever>
<sku_number>4</sku_number>
<first_name>john</first_name>
<last_name>coltrane</last_name>
</whatever>
</stuff_to_track>
I also recommend validating the incoming XML, for both schema and data.
Create a class and accept a list of that class. Be sure to mark it as [Serializable].
[Serializable]
public class Whatever
{
public int SkuNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Best practice would be to define the class in an assembly that can be accessed by both the service and the project that calls it.
The trouble with a tuple or a multi-dimensional array is that the data you send doesn't have an inherent identity: you could stick any old thing in there. If you have a class, you are indicating that you are sending an Order or an Inquiry or a Coupon or whatever it is you are tracking. There's a level of meaning that goes along with it.
Just send what you want:
public class Whatever
{
public int SkuNumber { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
[WebMethod]
public void TakeList(List<Whatever> theList)
{
foreach (var w in theList)
{
}
}

custom Serialize Class to JSon - for really small data

well,
i have a really simple class:
public class Channel : IContent
{
public Guid Guid { get; set; }
public string Title { get; set; }
public string Alias { get; set; }
public void Set()
{
// SAVE JSON DATA
using (System.IO.StreamWriter writer = new System.IO.StreamWriter(string.Join("", this.Guid, ".jsv")))
{
writer.Write("{ \"Title\":", this.Title, "}");
}
}
}
Serializing i could do as shown in code,
but how can i load that back into the class? that is the actual question.
please dont refer me to JavaScriptSerializer or DataContracts etc...
again, this should be so simple... that i dont think i need all those.
Json.NET. No, seriously you need a JSON serializer. Just pick one that you like. The 3 that have been listed work great. And make sure you read this article to better understand why you need a JSON serializer.

Categories

Resources