I'm building a program for my school's swim lesson organization, and I'm saving the data using XML serialization, but I keep getting an error every time I try to deserialize the data, it says "Runtime Error: Attempting to Deserialize an Empty Stream."
Here is my code to deserialize the file and put it into a window.
public StudentProfile()
{
InitializeComponent();
using (var file = File.Open(FindStudent.studentName + ".xml", FileMode.OpenOrCreate))
{
var bformatter = new BinaryFormatter();
var mp = (Person)bformatter.Deserialize(file);
file.Close();
nameBox.Text += mp.studentName;
parentBox.Text += mp.parentName;
yearBox.Text += mp.year;
semesterBox.Text += mp.semester;
sessionBox.Text += mp.session;
ageGroupBox.Text += mp.ageGroup;
sessionTimeBox.Text += mp.sessionTime;
levelBox.Text += mp.level;
paymentTypeBox.Text += mp.paymentType;
amountBox.Text += mp.amount;
checkNumberBox.Text += mp.checkNumber;
datePaidBox.Text += mp.datePaid;
}
}
I've tried some solutions on here, BinaryFormatter: SerializationException, but it still doesn't work. Can you guys help me?
Edit: I solved my error, using a different method, here is the code I ended up using to deserialize it. If anyone wants the serialization code, then I'll give it
Stream file = File.Open(#"C:\Swimmers\" + FindStudent.studentName + ".xml", FileMode.Open);
BinaryFormatter bformatter = new BinaryFormatter();
Person mp = (Person)bformatter.Deserialize(file);
file.Close();
nameBox.Text += mp.studentName;
parentBox.Text += mp.parentName;
yearBox.Text += mp.year;
semesterBox.Text += mp.semester;
sessionBox.Text += mp.session;
ageGroupBox.Text += mp.ageGroup;
sessionTimeBox.Text += mp.sessionTime;
levelBox.Text += mp.level;
paymentTypeBox.Text += mp.paymentType;
amountBox.Text += mp.amount;
checkNumberBox.Text += mp.checkNumber;
datePaidBox.Text += mp.datePaid;
}
With a FileMode of OpenOrCreate, if the file hasn't existed yet, it creates the file with no content, and thus would fail deserialization. It would be better to use:
if (File.Exists(FindStudent.StudentName + ".xml"))
{
//Serialization logic
}
else
{
//default logic; create the file but don't deserialize
//expect the UI to be loaded blank
}
That is probably the error you are experiencing, because you are deserializing a newly created blank file.
I strongly recommend you System.Runtime.Serialization Namespace, from System.Runtime.Serialization.dll. It provides serializers implementation such as XML and JSON.
The following example uses the DataContractSerializer.
[DataContract]
public class Student
{
[DataMember]
public string Name { get; set; }
[DataMember]
public int Age { get; set; }
public void Save(string filePath)
{
using (var fs = File.Open(filePath, FileMode.Create))
{
DataContractSerializer serializer = new DataContractSerializer(typeof (Student));
serializer.WriteObject(fs, this);
}
}
public static Student Load(string filePath)
{
Student result = null; //or default result
try
{
using (var fs = File.OpenRead(filePath))
{
DataContractSerializer serializer = new DataContractSerializer(typeof (Student));
result = serializer.ReadObject(fs) as Student;
}
}
catch (Exception)
{
}
return result;
}
}
Usage example:
...
var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "student1.xml");
var student = new Student
{
Name = "Student1",
Age = 10
};
student.Save(filePath);
var studentFromFile = Student.Load(filePath);
...
I hope it helps.
Related
I use EventLogWatcher to track events from logs. I need to save/restore bookmark events after my program stopped/started. Bookmark of events is represented by EventBookmark class https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.eventing.reader.eventbookmark?view=netframework-4.8 and it is inherited from ISerializable interface. BUT I cannot serialize object of that class. Because it does not have default ctor, ctor with no parameters.
I am not familiar with C# very good because I am using it during a week. Can you help me please to understand why this class inherited from ISerializable interface while I cannot serialize an object of that class? It looks strange for me like I do not understand something but I would like to know that.
Moreover contructor
EventBookmark(SerializationInfo, StreamingContext) and GetObjectData(SerializationInfo, StreamingContext) are protected.
So, how to serialize and deserialize object of EventBookmark class?
UPDATED (working code), thank you jdweng for help
namespace ConsoleApp
{
using System;
public class Program
{
static void Main(string[] args)
{
string logName = "Application";
string filter = "*";
System.Diagnostics.Eventing.Reader.EventLogQuery eventQuery = new System.Diagnostics.Eventing.Reader.EventLogQuery(
logName,
System.Diagnostics.Eventing.Reader.PathType.LogName,
filter
);
System.Diagnostics.Eventing.Reader.EventLogReader eventLogReader = new System.Diagnostics.Eventing.Reader.EventLogReader(eventQuery);
System.Diagnostics.Eventing.Reader.EventRecord eventRecord = eventLogReader.ReadEvent();
System.Diagnostics.Eventing.Reader.EventBookmark eventBookmark = eventRecord.Bookmark;
if (eventBookmark != null)
{
System.Runtime.Serialization.IFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
System.IO.Stream stream = new System.IO.MemoryStream();
formatter.Serialize(stream, eventBookmark);
stream.Position = 0;
System.IO.StreamReader reader = new System.IO.StreamReader(stream);
string result = reader.ReadToEnd();
Console.WriteLine("event bookmark: {0}", result);
stream.Position = 0;
System.Diagnostics.Eventing.Reader.EventBookmark newEventBookmark = (System.Diagnostics.Eventing.Reader.EventBookmark)formatter.Deserialize(stream);
if (newEventBookmark != null/* && eventBookmark.Equals(newEventBookmark)*/)
{
Console.WriteLine("Deserialization successfully passed");
}
else
{
Console.WriteLine("Deserialization failed");
}
}
else
{
Console.WriteLine("cannot read event bookmark");
}
Console.ReadKey();
}
}
}
I was able to create an XML serializer in Unity which saves my game state and was working fine. I haven't touched it since then, but a few days later it started saving the XML files on one line instead.
This was how it looked before:
<GameData xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<gameFlags/>
<memories/>
<lastSaveTime>-8586819040232916923</lastSaveTime>
<health>50</health>
<speed>0.27</speed>
<playerPosition>
<x>10</x>
<y>-48.2583</y>
<z>0</z>
</playerPosition>
<lastSaveFile/>
<savedScene>Area 0-0</savedScene>
<playerName>Jon</playerName>
<PlayerPositionX>10</PlayerPositionX>
<PlayerPositionY>-48.2583</PlayerPositionY>
<PlayerPositionZ>0</PlayerPositionZ>
</GameData>
This is how it looks now
-8586812865931894767 50 0.27 10 -48.2583 0 Default Area 0-0 Jon 10 -48.2583 0
Everything is all on one line. I reverted to my previous commit as well when it was working but it still is saving on one line. Does anyone have any experience with this change?
Save Game Method
public void SaveGame(string saveFile)
{
CheckDirectory();
gameData.generateGameData (GameObject.FindGameObjectWithTag("Player").GetComponent<Player>());
// Update saveFile name
if (saveFile == null)
{
saveFile = GenerateNewSaveName();
}
this.saveFile = saveFile;
// FileStream fs = File.Create(GameDic.Instance.SavePath + saveFile);
UpdateSaveData(saveFile);
string fullSavePath = SavePath + saveFile + FILE_EXTENSION;
FileStream fs;
// Create a file or open an old one up for writing to
if (!File.Exists(fullSavePath))
{
fs = File.Create(fullSavePath);
}
else
{
fs = File.OpenWrite(fullSavePath);
}
XmlSerializer serializer = new XmlSerializer(typeof(GameData));
TextWriter textWriter = new StreamWriter(fs, System.Text.Encoding.UTF8);
serializer.Serialize(textWriter, gameData);
fs.Close();
Debug.Log("Game Saved to " + fullSavePath);
}
GameData Class
[Serializable]
public class GameData
{
#region Public Fields
public List<GameFlag> gameFlags;
public List<string> memories;
public List<string> conversations;
public List<QuestInstance> questInstances;
public long lastSaveTime;
public float health, speed;
// Needs properties to access
[NonSerialized]
public Vector3 playerPosition;
public string lastSaveFile;
public string savedScene;
public string playerName;
#endregion Public Fields
#region Public Constructors
public void generateGameData(Player player) {
health = player.getStat (Stat.HP);
speed = player.getStat (Stat.SPEED);
savedScene = SceneManager.GetActiveScene ().name;
playerPosition = player.respawnPoint;
playerName = "Jon";
memories = player.GetComponentInChildren<MemoryManager> ().getAccessedMemories ();
conversations = player.GetComponentInChildren<NPCManager> ().getConversations ();
questInstances = player.GetComponent<QuestManager> ().getQuestInstances ();
}
public string GenerateNewSaveName()
{
int attempt = 0;
string newSaveName = "";
while (newSaveName == "")
{
// Save Name is Player Name
string checkString = gameData.playerName;
// Add a number if original already taken
if (attempt != 0) checkString += attempt;
if (!File.Exists(SavePath + checkString))
{
// Make the check string the new file name
newSaveName = checkString;
}
attempt++;
}
return newSaveName;
}
}
For the QuestInstance class I suppose it just can just be any filler class that's empty. It's serialized correctly.
Your problem is most likely this line:
fs = File.OpenWrite(fullSavePath);
If the previous file was longer than the new one, you will still have fragments from the previous save file at the end - that's why you can't deserialize it. Get rid of everything in that if condition except for the File.Create and it should work.
I have a console application using web services. What I am trying to achieve its to extract several information about projects which works fine.
The web service GetProjectData() outputs several parameters and one of those its the status.
I have created a xml file with the status that I need because the GetProjectData() does not take any arguments.
When I run the following code I have an error There is an error in XML document (0, 0).
Can you please point me how to achieve this?
Thanks
This is the xml created
<root>
<status>
<List>Published</List>
<List>expired</List>
</status>
</root>
Here is my code
class ProjectEqualityComparer : IEqualityComparer<WS.ProjectMetaData>
{
#region IEqualityComparer<Project> Members
public bool Equals(WS.ProjectMetaData x, WS.ProjectMetaData y)
{
return x.ProjectID.Equals(y.ProjectID);
}
public int GetHashCode(WS.ProjectMetaData obj)
{
return obj.ProjectID.GetHashCode();
}
#endregion
}
[XmlRoot("root")]
public class ListOFStatus
{
public ListOFStatus() { Items = new List<Status>(); }
[XmlElement("status")]
public List<Status> Items { get; set; }
}
public class Status
{
[XmlElement("List")]
public String Name { get; set; }
}
string customerList = "customerList";
string outCsvFile = string.Format(#"C:\\customerList\\{0}.csv", customerList + DateTime.Now.ToString("_yyyyMMdd HHmms"));
XmlSerializer ser = new XmlSerializer(typeof(ListOFStatus));
List<ListOFStatus> list = new List<ListOFStatus>();
object obj = null;
WS.ProjectData[] pr = db.GetProjectData();
using (FileStream fs = new FileStream(outCsvFile, FileMode.Append, FileAccess.Write))
using (StreamWriter file = new StreamWriter(fs))
{
file.WriteLine("ProjectID, ProjectTitle,PublishStatus,NumberOfCustomers,ProjectStartDate,ProjectEndDate, URL");
foreach(WS.ProjectData proj in pr.Distinct(new ProjectEqualityComparer()))
{
var userIDs = db.GetList(proj.ProjectID);
file.WriteLine("{0},\"{1}\",{2},{3},{4},{5},{6}",
proj.ProjectID,
proj.ProjectTitle,
obj = ser.Deserialize(fs), //where I need to display the status from the projects either Published or expired
userIDs.Length.ToString(NumberFormatInfo.InvariantInfo),
proj.ProjectStartDate.ToString(DateTimeFormatInfo.InvariantInfo),
proj.ProjectEndDate.ToString(DateTimeFormatInfo.InvariantInfo),
url[i].ToString());
}
}
Here you have my expected output.
You are trying to deserialize from your output file. You have
using (FileStream fs = new FileStream(outCsvFile, FileMode.Append, FileAccess.Write))
// ...
{
// ...
obj = ser.Deserialize(fs);
// ...
}
Since the FileStream is opened in Append Mode, there is nothing to read.
Furthermore, you are writing to that same file, which seems very wrong.
UPDATE:
You would need to do something along the lines of:
using (FileStream xml = File.OpenRead("xmlfilename"))
using (FileStream fs = new FileStream(outCsvFile, FileMode.Append, FileAccess.Write))
// ...
{
// ...
obj = ser.Deserialize(xml);
// ...
}
There is an error in XML document (8, 20). Inner 1: Unexpected XML declaration. The XML declaration must be the first node in the document, and no white space characters are allowed to appear before it.
OK, I understand this error.
How I get it, however, is what perplexes me.
I create the document with Microsoft's Serialize tool. Then, I turn around and attempt to read it back, again, using Microsoft's Deserialize tool.
I am not in control of writing the XML file in the correct format - that I can see.
Here is the single routine I use to read and write.
private string xmlPath = System.Web.Hosting.HostingEnvironment.MapPath(WebConfigurationManager.AppSettings["DATA_XML"]);
private object objLock = new Object();
public string ErrorMessage { get; set; }
public StoredMsgs Operation(string from, string message, FileAccess access) {
StoredMsgs list = null;
lock (objLock) {
ErrorMessage = null;
try {
if (!File.Exists(xmlPath)) {
var root = new XmlRootAttribute(rootName);
var serializer = new XmlSerializer(typeof(StoredMsgs), root);
if (String.IsNullOrEmpty(message)) {
from = "Code Window";
message = "Created File";
}
var item = new StoredMsg() {
From = from,
Date = DateTime.Now.ToString("s"),
Message = message
};
using (var stream = File.Create(xmlPath)) {
list = new StoredMsgs();
list.Add(item);
serializer.Serialize(stream, list);
}
} else {
var root = new XmlRootAttribute("MessageHistory");
var serializer = new XmlSerializer(typeof(StoredMsgs), root);
var item = new StoredMsg() {
From = from,
Date = DateTime.Now.ToString("s"),
Message = message
};
using (var stream = File.Open(xmlPath, FileMode.Open, FileAccess.ReadWrite)) {
list = (StoredMsgs)serializer.Deserialize(stream);
if ((access == FileAccess.ReadWrite) || (access == FileAccess.Write)) {
list.Add(item);
serializer.Serialize(stream, list);
}
}
}
} catch (Exception error) {
var sb = new StringBuilder();
int index = 0;
sb.AppendLine(String.Format("Top Level Error: <b>{0}</b>", error.Message));
var err = error.InnerException;
while (err != null) {
index++;
sb.AppendLine(String.Format("\tInner {0}: {1}", index, err.Message));
err = err.InnerException;
}
ErrorMessage = sb.ToString();
}
}
return list;
}
Is something wrong with my routine? If Microsoft write the file, it seems to me that it should be able to read it back.
It should be generic enough for anyone to use.
Here is my StoredMsg class:
[Serializable()]
[XmlType("StoredMessage")]
public class StoredMessage {
public StoredMessage() {
}
[XmlElement("From")]
public string From { get; set; }
[XmlElement("Date")]
public string Date { get; set; }
[XmlElement("Message")]
public string Message { get; set; }
}
[Serializable()]
[XmlRoot("MessageHistory")]
public class MessageHistory : List<StoredMessage> {
}
The file it generates doesn't look to me like it has any issues.
I saw the solution here:
Error: The XML declaration must be the first node in the document
But, in that case, it seems someone already had an XML document they wanted to read. They just had to fix it.
I have an XML document created my Microsoft, so it should be read back in by Microsoft.
The problem is that you are adding to the file. You deserialize, then re-serialize to the same stream without rewinding and resizing to zero. This gives you multiple root elements:
<?xml version="1.0"?>
<StoredMessage>
</StoredMessage
<?xml version="1.0"?>
<StoredMessage>
</StoredMessage
Multiple root elements, and multiple XML declarations, are invalid according to the XML standard, thus the .NET XML parser throws an exception in this situation by default.
For possible solutions, see XML Error: There are multiple root elements, which suggests you either:
Enclose your list of StoredMessage elements in some synthetic outer element, e.g. StoredMessageList.
This would require you to load the list of messages from the file, add the new message, and then truncate the file and re-serialize the entire list when adding a single item. Thus the performance may be worse than in your current approach, but the XML will be valid.
When deserializing a file containing concatenated root elements, create an XML writer using XmlReaderSettings.ConformanceLevel = ConformanceLevel.Fragment and iteratively walk through the concatenated root node(s) and deserialize each one individually as shown, e.g., here. Using ConformanceLevel.Fragment allows the reader to parse streams with multiple root elements (although multiple XML declarations will still cause an error to be thrown).
Later, when adding a new element to the end of the file using XmlSerializer, seek to the end of the file and serialize using an XML writer returned from XmlWriter.Create(TextWriter, XmlWriterSettings)
with XmlWriterSettings.OmitXmlDeclaration = true. This prevents output of multiple XML declarations as explained here.
For option #2, your Operation would look something like the following:
private string xmlPath = System.Web.Hosting.HostingEnvironment.MapPath(WebConfigurationManager.AppSettings["DATA_XML"]);
private object objLock = new Object();
public string ErrorMessage { get; set; }
const string rootName = "MessageHistory";
static readonly XmlSerializer serializer = new XmlSerializer(typeof(StoredMessage), new XmlRootAttribute(rootName));
public MessageHistory Operation(string from, string message, FileAccess access)
{
var list = new MessageHistory();
lock (objLock)
{
ErrorMessage = null;
try
{
using (var file = File.Open(xmlPath, FileMode.OpenOrCreate))
{
list.AddRange(XmlSerializerHelper.ReadObjects<StoredMessage>(file, false, serializer));
if (list.Count == 0 && String.IsNullOrEmpty(message))
{
from = "Code Window";
message = "Created File";
}
var item = new StoredMessage()
{
From = from,
Date = DateTime.Now.ToString("s"),
Message = message
};
if ((access == FileAccess.ReadWrite) || (access == FileAccess.Write))
{
file.Seek(0, SeekOrigin.End);
var writerSettings = new XmlWriterSettings
{
OmitXmlDeclaration = true,
Indent = true, // Optional; remove if compact XML is desired.
};
using (var textWriter = new StreamWriter(file))
{
if (list.Count > 0)
textWriter.WriteLine();
using (var xmlWriter = XmlWriter.Create(textWriter, writerSettings))
{
serializer.Serialize(xmlWriter, item);
}
}
}
list.Add(item);
}
}
catch (Exception error)
{
var sb = new StringBuilder();
int index = 0;
sb.AppendLine(String.Format("Top Level Error: <b>{0}</b>", error.Message));
var err = error.InnerException;
while (err != null)
{
index++;
sb.AppendLine(String.Format("\tInner {0}: {1}", index, err.Message));
err = err.InnerException;
}
ErrorMessage = sb.ToString();
}
}
return list;
}
Using the following extension method adapted from Read nodes of a xml file in C#:
public partial class XmlSerializerHelper
{
public static List<T> ReadObjects<T>(Stream stream, bool closeInput = true, XmlSerializer serializer = null)
{
var list = new List<T>();
serializer = serializer ?? new XmlSerializer(typeof(T));
var settings = new XmlReaderSettings
{
ConformanceLevel = ConformanceLevel.Fragment,
CloseInput = closeInput,
};
using (var xmlTextReader = XmlReader.Create(stream, settings))
{
while (xmlTextReader.Read())
{ // Skip whitespace
if (xmlTextReader.NodeType == XmlNodeType.Element)
{
using (var subReader = xmlTextReader.ReadSubtree())
{
var logEvent = (T)serializer.Deserialize(subReader);
list.Add(logEvent);
}
}
}
}
return list;
}
}
Note that if you are going to create an XmlSerializer using a custom XmlRootAttribute, you must cache the serializer to avoid a memory leak.
Sample fiddle.
I've got this code.
const int maxbooks = 5;
Book[] booklist = new Book[maxbooks];
FileStream fs = File.Open(#"books.txt", FileMode.Open, FileAccess.Read);
SoapFormatter sf = new SoapFormatter();
try
{
// something here, deserializing file and assigning to the array
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
finally
{
fs.Close();
}
I've figured out (or at least, I THINK I've figured out) how to serialize the original array of objects in a separate program. I'm now looking to de serialize that, and create a new array with the de serialized data. Just for reference, here's the other file where I serialized the original array
Book firstbook = new Book("Jimbob", "Jimbob book", "first edition", "Jimbob publishing", "1991");
Book secondbook = new Book("Greg", "Greg book", "third edition", "Unholy publishing", "2010");
Book thirdbook = new Book("Pingu", "Pingu book", "tenth edition", "Antarctic publishing", "1897");
Book fourthbook = new Book("Patrick", "Patrick book", "seventh edition", "underwater publishing", "1991");
Book fifthbook = new Book("Sally", "Sally book", "first edition", "Wowpublishing", "2015");
const int maxbooks = 5;
Book[] booklist = new Book[maxbooks];
booklist[0] = firstbook;
booklist[1] = secondbook;
booklist[2] = thirdbook;
booklist[3] = fourthbook;
booklist[4] = fifthbook;
// writing to a file
FileStream fs = File.Open(#"books.txt", FileMode.Create, FileAccess.Write);
SoapFormatter sf = new SoapFormatter();
int bookindex = 0;
try
{
while (bookindex < maxbooks)
{
sf.Serialize(fs, booklist[bookindex]);
bookindex += 1;
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
finally
{
fs.Close();
}
Using SOAP serialization for the moment with this. Any help will be appreciated.
Serialise the array itself rather than per item. With your serialiser routine you are creating many serialisation chunks that are not valid together appended in a file.
Use the XMLSerializer
For e.g.
Serialize like this:
private async Task SaveSettings(Settings settings)
{
var folder = Windows.Storage.ApplicationData.Current.LocalFolder;
var options = Windows.Storage.CreationCollisionOption.ReplaceExisting;
var file = await folder.CreateFileAsync("Settings.XML", options);
try
{
XmlSerializer SerializerObj = new XmlSerializer(typeof(Settings));
SerializerObj.Serialize(await file.OpenStreamForWriteAsync(), settings);
}
catch
{
// handle any kind of exceptions
}
}
Deserialize like this:
private async Task<Settings> LoadSettings()
{
Settings settings = new Settings();
var folder = Windows.Storage.ApplicationData.Current.LocalFolder;
try
{
var file = await folder.GetFileAsync("Settings.XML");
XmlSerializer SerializerObj = new XmlSerializer(typeof(Settings));
settings = SerializerObj.Deserialize(await file.OpenStreamForReadAsync()) as Settings;
}
catch (Exception ex)
{
// handle any kind of exceptions
}
return settings;
}
This example serializes an object called Settings. You can change it to serialize your array of objects.
This is from a windows 8 app so you may need to adapt it slightly.