I have the following code to serialize decrypted and deserialize encrypted objects from a binary file. Writing decrypted objects works well. However, when attempting to de-serialize decrypted objects from the file, it fails after the first object successfully de-serialized.
I was hoping that someone could point me to the error, as I ran out of ideas.
Writing encrypted objects to a binary file works perfectly well
public List ReadAllObjectsFromFile(string pPath)
{
List objList = null;
T obj = default(T);
using (FileStream stream = new FileStream(pPath, FileMode.Open, FileAccess.Read))
{
while (true)
{
if (stream.Length>m_ReaderPosition)
{
stream.Seek(m_ReaderPosition, SeekOrigin.Begin);
if (IsEncrypted)
{
using (Stream cryptoStream = new CryptoStream(stream, m_Decryptor, CryptoStreamMode.Read))
{
if (objList == null) objList = new List();
obj = (T)m_Formatter.Deserialize(cryptoStream);
}
}
else
{
if (objList == null) objList = new List();
obj = (T)m_Formatter.Deserialize(stream);
}
m_ReaderPosition = stream.Position;
}
if ((IsReadToEnd = object.Equals(obj, default(T)))) break;
else
{
objList.Add(obj);
obj = default(T);
}
}
}
return objList;
}
Trying to read the encrypted objects from the binary file and deserialize them into de-crypted objects throw as exception at the second object it attempts to deserialize
"System.Runtime.Serialization.SerializationException: 'The input
stream is not a valid binary format. The starting contents (in bytes)
are: 83-AD-D4-BB-F9-7A-4E-34-C2-E7-4F-0C-4F-51-F2-1E-EC .."
The method successfully de-serialized the first object though.
This line of code triggers the exception on second object.
obj = (T)m_Formatter.Deserialize(cryptoStream);
public List ReadAllObjectsFromFileEnc(string pPath)
{
List objList = null;
T obj = default(T);
using (FileStream stream = new FileStream(pPath, FileMode.Open, FileAccess.Read))
{
using (Stream cryptoStream = new CryptoStream(stream, m_Decryptor, CryptoStreamMode.Read))
{
while (true)
{
if (stream.Length>m_ReaderPosition)
{
stream.Seek(m_ReaderPosition, SeekOrigin.Begin);
if (objList == null) objList = new List();
obj = (T)m_Formatter.Deserialize(cryptoStream);
m_ReaderPosition = stream.Position;
}
if ((IsReadToEnd = object.ReferenceEquals(obj, default(T)))) break;
else
{
objList.Add(obj);
obj = default(T);
}
}
}
return objList;
}
}
Main
static void Main(string[] args)
{
List objList = new List();
objList.Add(
new Bear()
{
Name = "John",
Age = 35,
Birth = new DateTime(1977, 02, 02),
Females = new KeyValuePair("Dove", "Mumu")
});
objList.Add(
new Bear()
{
Name = "Max",
Age = 40,
Birth = new DateTime(1977, 08, 02),
Females = new KeyValuePair("Gr", "Mumu")
});
objList.Add(
new Bear()
{
Name = "Mika",
Age = 21,
Birth = new DateTime(1990, 02, 02),
Females = new KeyValuePair("Dr", "Mumu")
});
objList.Add(
new Bear()
{
Name = "Miles",
Age = 90,
Birth = new DateTime(1901, 02, 02),
Females = new KeyValuePair("SE", "Mumu")
});
BinarySerializer binser = new BinarySerializer(#"E:\Temp\myFile.bin", 10000, true, "My Encryption is here");
foreach(Bear t in objList)
binser.WriteObjectToFile(binser.FileDetails.FullName, t);
objList = null;
objList = binser.ReadAllObjectsFromFileEnc(binser.FileDetails.FullName);
}
Here's the write to file
public void WriteObjectToFile(string pPath, object pObject)
{
using (FileStream stream = new FileStream(pPath, FileMode.Append, FileAccess.Write))
{
if (IsEncrypted)
{
using (Stream cryptoStream = new CryptoStream(stream, m_Encryptor, CryptoStreamMode.Write))
{
// 3. write to the cryptoStream
m_Formatter.Serialize(cryptoStream, pObject);
}
}
else
m_Formatter.Serialize(stream, pObject);
}
}
Related
I'm trying to serialize/deserialize a string array in c# but getting this error -
System.Runtime.Serialization.SerializationException : End of Stream
encountered before parsing was completed.
Here is my code:
void SerializeFunc(ISession session, string key, object toSerialize)
{
var binaryFormatter = new BinaryFormatter();
var memoryStream = new MemoryStream();
binaryFormatter.Serialize(memoryStream, toSerialize);
session.Set(key, memoryStream.ToArray());
memoryStream.Flush();
memoryStream.Close();
memoryStream.Dispose();
}
object DeSerializeFunc(ISession session, string key)
{
var memoryStream = new MemoryStream();
var binaryFormatter = new BinaryFormatter();
var objectBytes = session.Get(key) as byte[];
memoryStream.Write(objectBytes, 0, objectBytes.Length);
memoryStream.Seek(0, SeekOrigin.Begin);
memoryStream.Position = 0;
var valueObject = binaryFormatter.Deserialize(memoryStream); //ERROR
memoryStream.Close();
memoryStream.Dispose();
return valueObject;
}
public class CustomSession : Microsoft.AspNetCore.Http.ISession
{
Dictionary<string, object> sessionStorage = new Dictionary<string, object>();
void ISession.Set(string key, byte[] value)
{
sessionStorage[key] = value;
}
bool ISession.TryGetValue(string key, out byte[] value)
{
if (sessionStorage[key] != null)
{
value = Encoding.ASCII.GetBytes(sessionStorage[key].ToString());
return true;
}
else
{
value = null;
return false;
}
}
// remove other Interface members for simplicity..
}
var session = new CustomSession();
SerializeFunc(session, "key1", new string[] { "one", "two", "three" });
var obj = DeSerializeFunc(session, "key1"); //ERROR
What am I doing wrong here?
I have a Razor page which I want to generate a Zip file containing multiple CSV files.
It works fine when I just want to generate one file, e.g.
public async Task<FileStreamResult> OnGet(int id)
{
var bankDetails = _paymentFileGenerator.GeneratePaymentFiles(id);
await using var memoryStream = new MemoryStream();
await using var streamWriter = new StreamWriter(memoryStream);
await using var csvWriter = new CsvWriter(streamWriter, CultureInfo.InvariantCulture)
{
Configuration = { HasHeaderRecord = false, }
};
csvWriter.WriteRecords(bankDetails);
streamWriter.Flush();
return new FileStreamResult(new MemoryStream(memoryStream.ToArray()), new MediaTypeHeaderValue("text/csv"))
{
FileDownloadName = "bacs.csv"
};
}
But when I try to pass memory streams for two files into a DotNetZip stream the zip downloads to the browser but both files are 0kb. Any thoughts on why?
public async Task<FileStreamResult> OnGet(int id)
{
var bankFiles = _paymentFileGenerator.GeneratePaymentFiles(id);
using var zipStream = new MemoryStream();
using var zip = new ZipFile();
await using var bankFileStream = new MemoryStream();
await using var bankFileStreamWriter = new StreamWriter(bankFileStream);
await using var bankFileCsvWriter = new CsvWriter(bankFileStreamWriter, CultureInfo.InvariantCulture)
{
Configuration = { HasHeaderRecord = false, }
};
bankFileCsvWriter.WriteRecords(bankFiles.BankFile);
bankFileCsvWriter.Flush();
bankFileStream.Seek(0, SeekOrigin.Begin);
zip.AddEntry("bacs.csv", (name, stream) => bankFileStream.ToArray());
await using var internalFileStream = new MemoryStream();
await using var internalFileStreamWriter = new StreamWriter(internalFileStream);
await using var internalFileCsvWriter = new CsvWriter(internalFileStreamWriter, CultureInfo.InvariantCulture);
internalFileCsvWriter.WriteRecords(bankFiles.InternalFile);
internalFileCsvWriter.Flush();
internalFileStream.Seek(0, SeekOrigin.Begin);
zip.AddEntry("internal.csv", (name, stream) => internalFileStream.ToArray());
zip.Save(zipStream);
zipStream.Seek(0, SeekOrigin.Begin);
return new FileStreamResult(new MemoryStream(zipStream.ToArray()), new MediaTypeHeaderValue("application/zip"))
{
FileDownloadName = "paymentbatch.zip"
};
}
I've seen other StackOverflow posts where people suggested adding the Seek() function to reset the position of the streams but it didn't work for me whether that was there or not.
When debugging, I can see that the 'bankfileStream' stream has bytes in it when I call the zip.AddEntry() but then the zipStream shows 0 bytes when I call zip.Save(zipStream).
Any suggestions appreciated!
I tried many different options and nothing worked until I used the SharpZipLib library instead. Here is the full solution:
public async Task<FileStreamResult> OnGet(int id)
{
var bankFiles = _paymentFileGenerator.GeneratePaymentFiles(id);
var bankFileBytes = await GetCsvFileBytes(bankFiles.BankFile, includeHeader: false);
var internalFileBytes = await GetCsvFileBytes(bankFiles.InternalFile);
var files = new List<AttachedFile>
{
new AttachedFile("bacs.csv", bankFileBytes),
new AttachedFile("internal.csv", internalFileBytes)
};
var zipStream = AddFilesToZip(files);
return new FileStreamResult(zipStream, new MediaTypeHeaderValue("application/zip"))
{
FileDownloadName = "paymentbatch.zip"
};
}
public MemoryStream AddFilesToZip(List<AttachedFile> attachedFiles)
{
var outputMemStream = new MemoryStream();
using (var zipStream = new ZipOutputStream(outputMemStream))
{
// 0-9, 9 being the highest level of compression
zipStream.SetLevel(3);
foreach (var file in attachedFiles)
{
var newEntry = new ZipEntry(file.Name) {DateTime = DateTime.Now};
zipStream.PutNextEntry(newEntry);
StreamUtils.Copy(new MemoryStream(file.Bytes), zipStream, new byte[4096]);
}
zipStream.CloseEntry();
// Stop ZipStream.Dispose() from also Closing the underlying stream.
zipStream.IsStreamOwner = false;
}
outputMemStream.Position = 0;
return outputMemStream;
}
private static async Task<byte[]> GetCsvFileBytes<T>(List<T> records, bool includeHeader = true) where T : class
{
await using var bankFileStream = new MemoryStream();
await using var bankFileStreamWriter = new StreamWriter(bankFileStream);
await using var bankFileCsvWriter = new CsvWriter(bankFileStreamWriter, CultureInfo.InvariantCulture)
{
Configuration = {HasHeaderRecord = includeHeader}
};
bankFileCsvWriter.WriteRecords(records);
bankFileStreamWriter.Flush();
return bankFileStream.ToArray();
}
public class AttachedFile
{
public byte[] Bytes { get; set; }
public string Name { get; set; }
public AttachedFile(string name, byte[] bytes)
{
Bytes = bytes;
Name = name;
}
}
I'm using this code
for (int i = 0; i < 3; ++i)
{
List<int> tl = new List<int>();
tl.Add(5);
tl.Add(4);
using (var fileStream = new FileStream(#"C:\file.dat", FileMode.Append))
{
var bFormatter = new BinaryFormatter();
bFormatter.Serialize(fileStream, tl);
//fileStream.Close();
}
var list = new List<int>();
using (var fileStream = new FileStream(#"C:\file.dat", FileMode.Open))
{
var bFormatter = new BinaryFormatter();
//while (fileStream.Position != fileStream.Length)
//{
// list.Add((int)bFormatter.Deserialize(fileStream));
//}
list = (List<int>)bFormatter.Deserialize(fileStream);
//fileStream.Close();
}
}
I expect .dat file will be
5 4 5 4 5 4
but it's only
5 4
also this code return also
5 4
List<int> tl = new List<int>();
tl.Add(5);
tl.Add(4);
using (var fileStream = new FileStream(#"C:\file.dat", FileMode.Append))
{
var bFormatter = new BinaryFormatter();
bFormatter.Serialize(fileStream, tl);
}
tl.Clear();
tl.Add(3);
tl.Add(2);
using (var fileStream = new FileStream(#"C:\file.dat", FileMode.Append))
{
var bFormatter = new BinaryFormatter();
bFormatter.Serialize(fileStream, tl);
}
var list = new List<int>();
using (var fileStream = new FileStream(#"C:\file.dat", FileMode.Open))
{
var bFormatter = new BinaryFormatter();
list = (List<int>)bFormatter.Deserialize(fileStream);
}
it looks like it de-serialize only first portion that was appended.
why data don't append?
UPDATE:
So the solution is:
var list = new List<int>();
using (var fileStream = new FileStream(#"C:\file.dat", FileMode.Open))
{
var bFormatter = new BinaryFormatter();
while (fileStream.Position != fileStream.Length)
{
var t = (List<int>)(bFormatter.Deserialize(fileStream));
list.AddRange(t);
}
}
You are adding three lists of ints, one after the other, and only reading the first one back. I think what your intent may be is to append to a (single) existing list, in which case you'd have to
Read your list back into memory
Add your new elements
Write the list back out to the file in overwrite (not append) mode
BinaryFormatter is not listed as being appendable. As it happens, you can usually get away with deserializing multiple times until you get to the EOF (and merging manually), but: there are other serializers which are explicitly designed to be appendable. For example, protocol buffers is an appendable format: concatenation is identical to merge. Further: if the outer element is a list, appending to the file is identical to adding to the composes list.
With protobuf-net, this is just:
for (int i = 0; i < 3; ++i)
{
List<int> tl = new List<int>();
tl.Add(5);
tl.Add(4);
using (var fileStream = new FileStream(#"C:\file.dat", FileMode.Append))
{
Serializer.Serialize(fileStream, tl);
}
using (var fileStream = new FileStream(#"C:\file.dat", FileMode.Open))
{
list = Serializer.Deserialize<List<int>>(fileStream);
}
}
At the end of each loop iteration, list (i.e. after deserialization) has 2, then 4, then 6 elements.
As Marc Gravell mentioned BinaryFormatter isn't appendable which means you'll need to reserialize every time you need to modify the file.
Example:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
class Program
{
const string file = #"C:\temp\file.dat";
static void Main()
{
for (int i = 0; i < 3; ++i)
{
List<int> tl = new List<int>();
tl.Add(5);
tl.Add(4);
AppendToDisk(tl);
}
var list = ReadFromDisk<int>();
foreach (var item in list)
{
Console.Write(item);
}
}
private static void AppendToDisk<T>(IEnumerable<T> collection)
{
var existing = ReadFromDisk<T>().ToList();
existing.AddRange(collection);
PersistToDisk(existing);
}
private static void PersistToDisk<T>(ICollection<T> value)
{
if (!File.Exists(file))
{
using (File.Create(file)) { };
}
var bFormatter = new BinaryFormatter();
using (var stream = File.OpenWrite(file))
{
bFormatter.Serialize(stream, value);
}
}
private static ICollection<T> ReadFromDisk<T>()
{
if (!File.Exists(file)) return Enumerable.Empty<T>().ToArray();
var bFormatter = new BinaryFormatter();
using (var stream = File.OpenRead(file))
{
return (ICollection<T>)bFormatter.Deserialize(stream);
}
}
}
I saved the data as shown below, now I want to restore the XML data into the corresponding text boxes.
CustomerData customer = new CustomerData();
customer.FirstName = first_name.Text;
customer.RegNo = reg_no.Text;
customer.Department = dept.Text;
XmlSerializer xs = new XmlSerializer(typeof(CustomerData));
using(FileStream fs = new FileStream(#"D:\Data.xml", FileMode.Create)) {
xs.Serialize(fs, customer);
}
MessageBox.Show("Inserted");
XmlSerializer xs = new XmlSerializer(typeof(CustomerData));
using(FileStream fs = new FileStream(#"D:\Data.xml", FileMode.Open))
{
CustomerData customer = (CustomerData)xs.Deserialize(fs);
first_name.Text = customer.FirstName;
reg_no.Text = customer.RegNo;
dept.Text = customer.Department;
}
UPDATE If you want to save history of customer data changes and load last one, then save and load list of CustomerData objects:
private List<CustomerData> GetCustomers(string filename)
{
if (!File.Exists(filename))
return new List<CustomerData>();
XmlSerializer xs = new XmlSerializer(typeof(List<CustomerData>));
using (FileStream fs = new FileStream(filename, FileMode.Open))
return (List<CustomerData>)xs.Deserialize(fs);
}
public void SaveCustomers(string filename, List<CustomerData> customers)
{
XmlSerializer xs = new XmlSerializer(typeof(List<CustomerData>));
using (FileStream fs = new FileStream(filename, FileMode.Create))
xs.Serialize(fs, customers);
}
And use it to save:
List<CustomerData> customers = GetCustomers(#"D:\Data.xml");
CustomerData customer = new CustomerData();
customer.FirstName = first_name.Text;
customer.RegNo = reg_no.Text;
customer.Department = dept.Text;
customers.Add(customer);
SaveCustomers(#"D:\Data.xml", customers);
And load:
var customer = GetCustomers(#"D:\Data.xml").LastOrDefault();
if (customer != null)
{
first_name.Text = customer.FirstName;
reg_no.Text = customer.RegNo;
dept.Text = customer.Department;
}
The following code sample shows how to serialize/deserialize to a file. How could I modify this to serialize to a variable instead of to a file? (Assume the variable would be passed in to the read/write methods instead of a file name).
public static void WriteObject(string fileName)
{
Console.WriteLine(
"Creating a Person object and serializing it.");
Person p1 = new Person("Zighetti", "Barbara", 101);
FileStream writer = new FileStream(fileName, FileMode.Create);
DataContractSerializer ser =
new DataContractSerializer(typeof(Person));
ser.WriteObject(writer, p1);
writer.Close();
}
public static void ReadObject(string fileName)
{
Console.WriteLine("Deserializing an instance of the object.");
FileStream fs = new FileStream(fileName,
FileMode.Open);
XmlDictionaryReader reader =
XmlDictionaryReader.CreateTextReader(fs, new XmlDictionaryReaderQuotas());
DataContractSerializer ser = new DataContractSerializer(typeof(Person));
// Deserialize the data and read it from the instance.
Person deserializedPerson =
(Person)ser.ReadObject(reader, true);
reader.Close();
fs.Close();
Console.WriteLine(String.Format("{0} {1}, ID: {2}",
deserializedPerson.FirstName, deserializedPerson.LastName,
deserializedPerson.ID));
}
You can change the FileStream to a memory stream and dump it to a byte[].
public static byte[] WriteObject<T>(T thingToSave)
{
Console.WriteLine("Serializing an instance of the object.");
byte[] bytes;
using(var stream = new MemoryStream())
{
var serializer = new DataContractSerializer(typeof(T));
serializer.WriteObject(stream, thingToSave);
bytes = new byte[stream.Length];
stream.Position = 0;
stream.Read(bytes, 0, (int)stream.Length);
}
return bytes;
}
public static T ReadObject<T>(byte[] data)
{
Console.WriteLine("Deserializing an instance of the object.");
T deserializedThing = default(T);
using(var stream = new MemoryStream(data))
using(var reader = XmlDictionaryReader.CreateTextReader(stream, new XmlDictionaryReaderQuotas()))
{
var serializer = new DataContractSerializer(typeof(T));
// Deserialize the data and read it from the instance.
deserializedThing = (T)serializer.ReadObject(reader, true);
}
return deserializedThing;
}